linux驱动之芯片级移植

一:底层基础概念

为了在soc上运行linux,需要提供大量的底层支持,定时器节拍,中断控制器,SMP启动,CPU的热插拔,底层的GPIO,时钟,pinctrl,DMA硬件的封装。

二:内核节拍驱动:内核是基于节拍设计,一般SOC将linux移植到自己芯片上,会从芯片内部找一个定时器,,并配置为HZ的频率,调用内核核心层的timer_tick函数,从而引发系统里的行为,eg:arch/arm/mach-hi3536/time.c

当前linux多采用无节拍方案,并且支持高精度定时器,无节拍意味着根据系统的运行情况,以事件驱动的方式动态决定下一个节拍在何时发生,,linux的底层被实现为clock_event_device和clocksource形式的驱动,clock_event_device实现其set_mode和set_next_event函数,clocksource结构主要实现read成员,定时器中断服务程序,调用clock_event_device和event_handle函数成员。

static void __init hi3536_clocksource_init(void __iomem *base, const char *name)
{
    long rate = sp804_get_clock_rate(name);
    struct clocksource *clksrc = &hi3536_clocksource.clksrc;

    if (rate < 0)
        return;

    clksrc->name   = name;
    clksrc->rating = 200;
    clksrc->read   = hi3536_clocksource_read;
    clksrc->mask   = CLOCKSOURCE_MASK(32),
    clksrc->flags  = CLOCK_SOURCE_IS_CONTINUOUS,
    clksrc->resume = hi3536_clocksource_resume,

    hi3536_clocksource.base = base;

    hi3536_clocksource_start(base);

    clocksource_register_hz(clksrc, rate);

    setup_sched_clock(hi3536_sched_clock_read, 32, rate);
}

hi3536_clocksource_read:该函数刻度处从开机到当前时刻定时器计数器已经走过的值,无论有没有设置当前计数器达到的某值产生的中断,硬件的技术总是在进行,该函数给linux提供了一个底层的准确参考值。

static struct clock_event_device sp804_clockevent = {(arch/arm/timer-sp.c)
    .features       = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
    .set_mode    = sp804_set_mode,
    .set_next_event    = sp804_set_next_event,
    .rating        = 300,
};

2;中断控制寄存器驱动

设备一般通过request_irq,local_irq_enable等API来完成中断的申请,使能,禁止等功能。在将linux移植到新的SOC时,芯片厂商需要提供部分API底层实现支持

local_irq_enable,local_irq_disable在中\arch\arm\include\asm\irqflags.h中配置。直接让cpu本身不响应中断请求。

disable_irq,enable_irq是针对适用于某个中断。

内核中通过irq_chip结构体来描述中断控制器,描述了中断mask,ack等成员函数,定义在/include/linux/irq.h中。芯片公司会将内部的中断控制寄存器实现为irq_chip驱动的形式,实现部分函数就可以了。查找中断的API接口/kernel/irq/chip.c文档中

在芯片内部,多个控制器之间还可以级联,举个例子,芯片内部有一个中断控制寄存器,支持32个中断源,4个来源于4组GPIO,每组gpio有32个中断源,这样会先试用1级中断号,而引脚本身的中断号在实现与GPIO控制器对应的irq_chip驱动时,我们又对其进行映射。多中断控制器的情况下,我们使用内核提供的通用的irq_chip驱动架构irq_chip_generic,(drivers/irqchip/rq-sirfsoc.c中)目前,主流的arm芯片内部的一级中断控制器都是用了arm公司的GIC,不需要代码,只在设备树中添加相关的节点就ok了。irq_chip的接口的配置方式:IRQCHIP_DECLARE(sirfsoc_intc,"sirf,prima2-intc",sirsoc_irq_init)

"sirf,prima2-intc":是设备树种中断控制器的compatible字段,sirfsoc_irq_init是匹配这个字段的初始化函数。

3:SMP多核启动和CPU热插拔。

对于多核ARM,每个cpu都有自己的自身ID。ID为0,则引导Bootloader和linux内核执行,如果不为0,则将自身置于WFI或者WFE状态,并等待CPU0给启发CPU核间中断唤醒它

例如通过命令启用cpu1 echo 1/sys/devices/system/cpu1/online之后CPU1会主动参与到系统中各个CPU之间进行运行任务的负载均衡。CPU0唤醒其他CPU的动作在内核中被封装为一个smp_operations的结构体,对于arm而言,它定义在arch/arm/include/asm/smp.h

struct smp_operations hi3536_smp_ops __initdata = { //ops操作文件/arch/arm/mach-hi3536
    .smp_init_cpus = hi3536_smp_init_cpus,
    .smp_prepare_cpus = hi3536_smp_prepare_cpus,
    .smp_secondary_init = hi3536_secondary_init,
    .smp_boot_secondary = hi3536_boot_secondary,
#ifdef CONFIG_HOTPLUG_CPU
    .cpu_die = hi3536_cpu_die,
#endif
};

4:GPIO驱动

在driver/gpio下实现了基于gpiolib的GPIO驱动,定义了一个通用于描述底层GPIO控制器的gpio_chip结构体,填充该结构体,最终调用gpiochip_add注册gpio_chip,GPIo驱动可以存在于drivers/gpio目录中,gpio驱动旺旺直接移植到drivers/pinctrl目录下并连同pinmux一起实现,而不存在于driver/gpio目录中。通过这层封装,每个具体要用到的GPIO的设备驱动都使用通用的GPIO API来操作GPIO,主要用于GPIO的申请,释放和设置。eg:gpio_request,gpio_set_value(这里只做部分API,如有需要查询资料。)

******对于GPIO而言,内核会创建/sys节点/sys/class/gpio/gpioN,通过它,我们可以echo值从而改变GPIO的方向,并且获取GPIO的值,在用于设备树的情况下,我们通过设备树来描述其GPIO控制器提供GPIO引脚被具体设备使用的情况。

5:pinctrl驱动

许多SOC内部都包含频控制器,通过频控制器的寄存器,我们可以配置一个或者一组引脚的功能。在pintctrl子系统注册一个pinctrl_desc描述符,

6:时钟驱动

在一个SOC中,晶振,PLL,驱动,门电路会形成一个时钟树形结构,有clk_get_rate,clk_set_rate,clk_get_parent,clk_set_parent等通用API,这些中的每个SOC需要单独实现,这样需要一个通用时钟框架来解决这个碎片化问题,我们称之为通用时钟,我们这里采用了一个统一的clk结构体,,这些API会调用统一的clk_ops中的回调函数,是关于时钟的使能,静止,计算评率等操作,

从clk核心层到具体芯片的clk驱动的调用顺序为:clk_enable(clk)--clk->ops->enable(clk->hw)

一般会在具体的驱动中定义针对特定clk的结构体(如foo),该结构体中包含clk_hw成员以及硬件私有数据:,在针对回调函数,我们通过调用最终获得硬件的私有数据。,并且访问硬件读写寄存改变始终的状态。通过clk_register来注册硬件上所有的clk,通过不同的clk类型,clk子系统又提供了几个快捷函数完成clk_register的过程。

内核提倡通过设备树来描述电路板上的时钟,以及时钟和设备之间的绑定关系,通过clk的节点#clock_cells属性,并且在clk驱动中通过of_clk_add_provider注册时钟控制器为一个时钟树的提供者。在具体的驱动设备中,通过调用clk API来操作所有的时钟,

 

 

 

 

 

 

你可能感兴趣的:(个人随笔)