前面我们已经完成了CCF子系统的分析,也说明了如何实现CCF驱动,本章为该专栏的最后一篇文章,
本章我们将实现一个虚拟的gpio clk gate驱动。本章大概分为如下几个章节:
一、 本次驱动开发涉及的知识点
二、clk provider driver的实现流程说明
三、gpio clk gate driver驱动设计
一、 本次驱动开发涉及的知识点
本次驱动实践主要涉及如下几个方面的知识点:
- Platform device、platform drvier
- Gpio 驱动使用
- Clk provider驱动开发流程
二、clk provider driver的实现流程说明
关于clk provider driver的开发流程,在上一篇文章中已经做了说明,此处再说明一下,主要实现流程包括如下两步:
- 完成clk的注册,主要是调用clk_register接口,完成上述章节一所述的内容;
- 完成该clk provider的map,这种map机制可以理解为定义了clk consumer与clk_provider的映射关系,即该clk provider可以给哪些clk consumer提供时钟(如针对非设备树模式,则定义了clk consumer的设备名称、clk consumer的时钟使用名称),而clk provider的map存在两种方式:
- 若linux不支持设备树机制,则通过调用接口clk_register_clkdev,完成这种映射操作(即完成下图中“非设备树模式下clk_core的map”)。
- 若linux支持设备树机制,则通过调用接口of_clk_add_provider,完成map操作(即完成下图中“设备树模式下clk_core的map”)
三、gpio clk gate driver驱动设计
本次我们主要实现gpio clk gate driver,本次实现的平台为ubuntu16.04,我们使用之前模拟的gpio chip
driver,提供gpio index,从而通过gpio 的值实现gpio clk gate driver。。另外针对clk gate,仅需要提供enable、disable、is_enabled接口即可。
相关数据结构
结构体struct virt_gpio_clk_gate作为本次gpio clk gate driver的数据结构,主要包含clk provier device、gpio index描述信息;
struct virt_gpio_clk_gate
{
struct gpio_desc *gpiod_clk_gate;
struct clk_hw hw;
};
而结构体gpio_clk_gate_platform_data主要用于传递platform device for clk provider的enable gpio index信息,因为此次我们使用ubuntu16.04进行验证,因此使用该模式传递gpio 信息,包含gpio index、是否低有效等信息。
struct gpio_clk_gate_platform_data
{
int enable_gpio_index;
int active_low;
};
gpio clk provider driver实现
Gpio clk provider driver的实现如下,主要完成clk provider device注册、clk_ops的实现(enable、disable、is_enabled)等。如下即为clk provider driver的probe接口,主要调用clk_register完成clk provider的注册、并调用of_clk_add_provider/clk_register_clkdev实现clk provider的map。
static int virt_gpio_clk_gate_probe(struct platform_device *pdev)
{
struct gpio_clk_gate_platform_data *pdata = (struct gpio_clk_gate_platform_data *)(pdev->dev.platform_data);
struct virt_gpio_clk_gate * gpio_clk_gate_ptr = NULL;
struct clk_init_data init_data;
unsigned long gate_gpio_flag = 0;
struct clk *clk;
int ret = 0;
#ifdef CONFIG_OF
int en_gpio = 0;
enum of_gpio_flags of_flags;
#endif
printk("%s %d\n", __FUNCTION__, __LINE__);
gpio_clk_gate_ptr = devm_kzalloc(&pdev->dev, sizeof(struct virt_gpio_clk_gate), GFP_KERNEL);
if (!gpio_clk_gate_ptr)
return -ENOMEM;
printk("%s %d\n", __FUNCTION__, __LINE__);
memset(&init_data, 0, sizeof(init_data));
init_data.num_parents = 0;
init_data.ops = &virt_gpio_clk_gate_ops;
init_data.name = pdev->name;
init_data.flags = CLK_IS_ROOT;
if(pdata != NULL)
{
gpio_clk_gate_ptr->gpiod_clk_gate = gpio_to_desc(pdata->enable_gpio_index);
gpio_clk_gate_ptr->hw.init = &init_data;
printk("%s %d\n", __FUNCTION__, __LINE__);
if(pdata->active_low)
gate_gpio_flag = GPIOF_ACTIVE_LOW | GPIOF_OUT_INIT_HIGH;
else
gate_gpio_flag = GPIOF_OUT_INIT_LOW;
}
#ifdef CONFIG_OF
if(pdev->dev.of_node != NULL)
{
en_gpio = of_get_named_gpio_flags(pdev->dev.of_node, "enable-gpio", 0, &of_flags);
if(en_gpio < 0)
return en_gpio;
if(of_flags & OF_GPIO_ACTIVE_LOW)
gate_gpio_flag = GPIOF_ACTIVE_LOW | GPIOF_OUT_INIT_HIGH;
else
gate_gpio_flag = GPIOF_OUT_INIT_LOW;
}
#endif
printk("%s %d gpio index=%d\n", __FUNCTION__, __LINE__, pdata->enable_gpio_index);
ret = devm_gpio_request_one(&pdev->dev, pdata->enable_gpio_index, gate_gpio_flag, pdev->name);
if (ret)
{
printk("%s %d ret=%d\n", __FUNCTION__, __LINE__, ret);
return ret;
}
printk("%s %d\n", __FUNCTION__, __LINE__);
clk = devm_clk_register(&pdev->dev, &gpio_clk_gate_ptr->hw);
if (IS_ERR(clk))
return -EINVAL;
printk("%s %d\n", __FUNCTION__, __LINE__);
if(pdev->dev.of_node != NULL)
return of_clk_add_provider(pdev->dev.of_node, of_clk_src_simple_get, clk);
else
return clk_register_clkdev(clk, "virt_gpio_gate", NULL);
printk("%s %d\n", __FUNCTION__, __LINE__);
}
static int virt_gpio_clk_gate_remove(struct platform_device *pdev)
{
printk("%s %d\n", __FUNCTION__, __LINE__);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id of_gpio_clk_gate_match[] = {
{ .compatible = "virt_gpio_clk", },
{},
};
#endif
static struct platform_driver gpio_clk_gate_driver = {
.probe = virt_gpio_clk_gate_probe,
.remove = virt_gpio_clk_gate_remove,
.driver = {
.name = "virt_gpio_clk",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(of_gpio_clk_gate_match),
},
};
module_platform_driver(gpio_clk_gate_driver);
MODULE_AUTHOR("jerry_chg");
MODULE_DESCRIPTION("gpio clk gate driver");
MODULE_LICENSE("GPL");
该gpio clk gate provider 的clk_ops操作接口定义如下:
#define to_clk_gpio(_hw) container_of(_hw, struct virt_gpio_clk_gate, hw)
static int virt_gpio_clk_gate_enable(struct clk_hw *hw)
{
struct virt_gpio_clk_gate *gpio_clk_gate_ptr = to_clk_gpio(hw);
gpiod_set_value(gpio_clk_gate_ptr->gpiod_clk_gate, 1);
return 0;
}
static void virt_gpio_clk_gate_disable(struct clk_hw *hw)
{
struct virt_gpio_clk_gate *gpio_clk_gate_ptr = to_clk_gpio(hw);
gpiod_set_value(gpio_clk_gate_ptr->gpiod_clk_gate, 0);
}
static int virt_gpio_clk_gate_is_enabled(struct clk_hw *hw)
{
struct virt_gpio_clk_gate *gpio_clk_gate_ptr = to_clk_gpio(hw);
return gpiod_get_value(gpio_clk_gate_ptr->gpiod_clk_gate);
}
static struct clk_ops virt_gpio_clk_gate_ops =
{
.enable = virt_gpio_clk_gate_enable,
.disable = virt_gpio_clk_gate_disable,
.is_enabled = virt_gpio_clk_gate_is_enabled,
};
gpio clk consumer driver实现
该consumer driver驱动主要用于验证gpio clk provider driver是否生效,我们在probe接口完成clk的enable操作;而在remove接口完成clk 的disable操作。接口实现如下:
static int virt_gpio_clk_consumer_probe(struct platform_device *pdev)
{
struct virt_gpio_consumer_data *pdata = (struct virt_gpio_consumer_data *)(pdev->dev.platform_data);
struct virt_clk_consumer *consumer_ptr = NULL;
printk("%s %d\n", __FUNCTION__, __LINE__);
consumer_ptr = devm_kzalloc(&pdev->dev, sizeof(struct virt_clk_consumer), GFP_KERNEL);
if (!consumer_ptr)
return -ENOMEM;
printk("%s %d\n", __FUNCTION__, __LINE__);
consumer_ptr->clk = clk_get(&pdev->dev, pdata->con_id);
if (IS_ERR(consumer_ptr->clk))
{
return PTR_ERR(consumer_ptr->clk);
}
printk("%s %d\n", __FUNCTION__, __LINE__);
platform_set_drvdata(pdev, consumer_ptr);
clk_prepare_enable(consumer_ptr->clk);
return 0;
}
static int virt_gpio_clk_consumer_remove(struct platform_device *pdev)
{
struct virt_clk_consumer *consumer_ptr = platform_get_drvdata(pdev);
clk_disable_unprepare(consumer_ptr->clk);
clk_put(consumer_ptr->clk);
printk("%s %d\n", __FUNCTION__, __LINE__);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id of_gpio_clk_consumer_match[] = {
{ .compatible = "virt_gpio_consumer", },
{},
};
#endif
static struct platform_driver gpio_clk_gate_driver = {
.probe = virt_gpio_clk_consumer_probe,
.remove = virt_gpio_clk_consumer_remove,
.driver = {
.name = "virt_gpio_consumer",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(of_gpio_clk_consumer_match),
},
};
module_platform_driver(gpio_clk_gate_driver);
MODULE_AUTHOR("jerry_chg");
MODULE_DESCRIPTION("gpio clk gate driver");
MODULE_LICENSE("GPL");
Virt gpio chip driver
该驱动使用的是gpio driver子系统专栏中实现的虚拟gpio驱动,本处不再细述,想要了解的童鞋请参考
之前的文章。
测试验证:
- 加载virt gpio 驱动
- insmod virt_gpio_dev.ko;
- insmod virt_gpio.ko
- 加载gpio clk gate 驱动
- insmod gpio_clk_platform.ko
- insmod gpio_clk_driver.ko
- 加载clk consumer device驱动
- insmod gpio_comsumer_device.ko
- 查看当前gpio的值:
- 加载clk consumer driver驱动
- insmod gpio_comsumer_driver.ko
- 查看当前gpio的值
此时gpio已经改变,clk已经使能;
- 卸载clk consumer driver驱动
- insmod gpio_comsumer_driver.ko
- 查看当前gpio的值
此时gpio已经修改为0,clk 已经关闭;
以上就是本章的主要内容,我们主要实现一个gpio clk gate provider driver,至此我们基本上已完成CCF子系统的学习,本章的驱动代码链接如下: