在一个新的ARM SoC上,如何移植Linux。
1、介绍Linux 3.x之后的内核在底层BSP上做了哪些优化。
2、如何提供操作系统的运行节拍。
3、中断控制器驱动以及它如何为驱动提供标准接口。
4、多核SMP芯片的启动。
5、作为Linux运行底层基础设施的GPIO、pinctrl(管脚控制器)、时钟和dmaengine驱动。
本章有助于工程师理解驱动调用的底层API的来源,以及直接进行Linux的平台移植。
20.1 ARM Linux底层驱动的组成和现状
让Linux在一个全新的ARM SoC上运行,需提供大量的底层支撑,如定时器节拍、中断控制器、SMP启动、CPU热插拔以及底层的GPIO、时钟、pinctrl和DMA硬件的封装等。定时器节拍为Linux基于时间片的调度机制以及内核和用户空间的定时器提供支撑,中断控制器的驱动则使得Linux内核的工程师可以直接调用local_irq_disable()、disable_irq()等通用的中断API,SMP启动支持则用于让SoC内部的多个CPU核都投入运行,CPU热插拔则运行运行时挂载或拔除CPU。
在GPIO、时钟、pinctrl和DMA驱动方面,在Linux 2.6时代,内核已或多或少有GPIO、时钟等底层驱动的架构,但是核心层的代码太薄弱,各SoC在这些基础设施实现方面存在巨大差异,而且每个SoC仍然需要实现大量的代码。pinctrl和DMA则最为混乱,几乎各家公司都定义了自己独特的实现和API。
社区必须改变这种局面,Linux社区在2011年后进行了如下工作,这些工作在目前的Linux内核中基本准备就绪:
1、STEricsson公司的工程师Linus Walleij提供新的pinctrl驱动架构,内核中新增加一个drivers/pinctrl目录,支撑SoC上的引脚复用,各个SoC的实现代码统一放入该目录。
2、TI公司的工程师Mike Turquette提供通用时钟框架,让具体SoC实现clk_ops()成员函数,并通过
clk_register()、clk_register_clkdev()注册时钟源以及源与设备的对应关系,具体的时钟驱动都统一迁
移到drivers/clk目录中。
3、建议各SoC统一采用dmaengine架构实现DMA驱动,该架构提供通用的DMA通道API,如
dmaengine_prep_slave_single()、dmaengine_submit()等,要求SoC实现dma_device的成员函数,实现代码统一放入drivers/dma目录中。
4、在GPIO方面,drivers/gpio下的gpiolib已能与新的pinctrl完美共存,实现引脚的GPIO和其他功能之间的复用,具体的SoC只需实现通用的gpio_chip结构体的成员函数。
经过以上工作,基本上就把芯片底层基础架构方面的驱动架构统一了,实现方法也统一了。目前GPIO、时钟、pinmux等都能良好地进行设备树的映射处理,可以方便地在.dts中定义一个设备要的时钟、pinmux引脚以及GPIO。
除了上述基础设施以外,在将Linux移植入新的SoC过程中,工程师常常强烈依赖于早期的printk功能,内核则提供了相关的DEBUG_LL和EARLY_PRINTK支持,只需要SoC提供商实现少量的回调函数或宏。
本章主要对上述各个组成部分进行架构上的剖析以及对关键的实现部分的实例分析,以求完整归纳出将Linux移植入新SoC的主要工作。
20.2 内核节拍驱动
Linux 2.6的早期(Linux2.6.21之前)内核是基于节拍设计的,一般SoC将Linux移植到芯片上时,会从芯片内部找一个定时器,并将该定时器配置为赫兹的频率,在每个时钟节拍到来时,调用ARM Linux内核核心层的timer_tick()函数,引发系统里的一系列行为。如Linux 2.6.17中arch/arm/mach-s3c2410/time.c的做法类似于代码清单20.1所示。
代码清单20.1 早期内核的节拍驱动
/*
* IRQ handler for the timer 定时器中断服务程序
*/
static irqreturn_t
s3c2410_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{undefined
write_seqlock(&xtime_lock);
timer_tick(regs);
write_sequnlock(&xtime_lock);
return IRQ_HANDLED;
}
static struct irqaction s3c2410_timer_irq = {undefined
.name = "S3C2410 Timer Tick",
.flags = SA_INTERRUPT | SA_TIMER,
.handler = s3c2410_timer_interrupt, // 中断处理函数
};
static void __init s3c2410_timer_init (void)
{undefined
s3c2410_timer_setup();
setup_irq(IRQ_TIMER4, &s3c2410_timer_irq);
}
分析:将硬件的TIMER4定时器配置为周期触发中断,每个中断到来会自动调用内核函数timer_tick()。
当前的Linux多采用无节拍方案,并支持高精度定时器,内核的配置一般会使能NO_HZ(即无节拍,或
者说动态节拍)和HIGH_RES_TIMERS。强调的是无节拍并不是说系统中没有时钟节拍,而是说这个节拍不再像以前那样周期性地产生。如果画一个时间轴,周期节拍的系统节拍中断发生的时序如图20.1所示:
无节拍意味着,根据系统的运行情况,以事件驱动的方式动态决定下一个节拍在何时发生。而NO_HZ(无节拍)的Linux的运行节拍如图20.2所示,看起来是:两次定时器中断发生的时间间隔可长可短:
在当前的Linux系统中,SoC底层的定时器被实现为一个clock_event_device和clocksource形式的驱动。在clock_event_device结构体中,实现其set_mode()和set_next_event()成员函数;在clocksource结构体
中,主要实现read()成员函数。而在定时器中断服务程序中,不再调用timer_tick(),而是调用clock_event_device的event_handler()成员函数。一个典型SoC的底层节拍定时器驱动形如代码清单20.2所
示。
代码清单20.2 新内核基于clocksource和clock_event的节拍驱动
static irqreturn_t xxx_timer_interrupt(int irq, void *dev_id)
{undefined
struct clock_event_device *ce = dev_id;
…
ce->event_handler(ce);
return IRQ_HANDLED;
}
/* read 64-bit timer counter */
static cycle_t xxx_timer_read(struct clocksource *cs)
{undefined
u64 cycles;
/* read the 64-bit timer counter */
cycles = readl_relaxed(xxx_timer_base + LATCHED_HI);
cycles=(cycles<<32)|readl_relaxed(xxx_timer_base + LATCHED_LO);
return cycles;
}
static int xxx_timer_set_next_event(unsigned long delta,
struct clock_event_device *ce)
{undefined
unsigned long now, next;
now = readl_relaxed(xxx_timer_base + LATCHED_LO);
next = now + delta;// 产生下一次节拍中断
writel_relaxed(next, xxx_timer_base + SIRFSOC_TIMER_MATCH_0);
...
}
static void xxx_timer_set_mode(enum clock_event_mode mode,
struct clock_event_device *ce)
{undefined
switch (mode) {undefined
case CLOCK_EVT_MODE_PERIODIC:
…
case CLOCK_EVT_MODE_ONESHOT:
…
case CLOCK_EVT_MODE_SHUTDOWN:
…
case CLOCK_EVT_MODE_UNUSED:
case CLOCK_EVT_MODE_RESUME:
break;
}
}
static struct clock_event_device xxx_clockevent = {undefined
.name = "xxx_clockevent",
.rating = 200,
.features = CLOCK_EVT_FEAT_ONESHOT,
.set_mode = xxx_timer_set_mode,
.set_next_event = xxx_timer_set_next_event,
};
static struct clocksource xxx_clocksource = {undefined
.name = "xxx_clocksource",
.rating = 200,
.mask = CLOCKSOURCE_MASK(64),
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
.read = xxx_timer_read,
.suspend = xxx_clocksource_suspend,
.resume = xxx_clocksource_resume,
};
static struct irqaction xxx_timer_irq = {undefined
.name = "xxx_tick",
.flags = IRQF_TIMER,
.irq = 0,
.handler = xxx_timer_interrupt,
.dev_id = &xxx_clockevent,
};
static void __init xxx_clockevent_init(void)
{undefined
clockevents_calc_mult_shift(&xxx_clockevent, CLOCK_TICK_RATE, 60);
xxx_clockevent.max_delta_ns =
clockevent_delta2ns(-2, &xxx_clockevent);
xxx_clockevent.min_delta_ns =
clockevent_delta2ns(2, &xxx_clockevent);
xxx_clockevent.cpumask = cpumask_of(0);
clockevents_register_device(&xxx_clockevent);
}
/* initialize the kernel jiffy timer source */
static void __init xxx_timer_init(void)
{undefined
…
BUG_ON(clocksource_register_hz(&xxx_clocksource, CLOCK_TICK_RATE));
BUG_ON(setup_irq(xxx_timer_irq.irq, &xxx_timer_irq));
xxx_clockevent_init();
}
struct sys_timer xxx_timer = {undefined
.init = xxx_timer_init,
};
特别关注如下的函数:
linux/clockchips.h
struct clock_event_device {undefined
void (*event_handler)(struct clock_event_device *);
int (*set_next_event)(unsigned long delta, struct clock_event_device *);
int (*set_next_ktime)(ktime_t expires, struct clock_event_device *);
ktime_t next_event;
u64 max_delta_ns;
u64 min_delta_ns;
u32 mult;
u32 shift;
enum clock_event_state state_use_accessors;
unsigned int features;
unsigned long retries;
int (*set_state_periodic)(struct clock_event_device *);
int (*set_state_oneshot)(struct clock_event_device *);
int (*set_state_oneshot_stopped)(struct clock_event_device *);
int (*set_state_shutdown)(struct clock_event_device *);
int (*tick_resume)(struct clock_event_device *);
void (*broadcast)(const struct cpumask *mask);
void (*suspend)(struct clock_event_device *);
void (*resume)(struct clock_event_device *);
unsigned long min_delta_ticks;
unsigned long max_delta_ticks;
const char *name;
int rating;
int irq;
int bound_on;
const struct cpumask *cpumask;
struct list_head list;
struct module *owner;
} ____cacheline_aligned;
linux/clocksource.h
struct clocksource {undefined
u64 (*read)(struct clocksource *cs);
u64 mask;
u32 mult;
u32 shift;
u64 max_idle_ns;
u32 maxadj;
#ifdef CONFIG_ARCH_CLOCKSOURCE_DATA
struct arch_clocksource_data archdata;
#endif
u64 max_cycles;
const char *name;
struct list_head list;
int rating;
int (*enable)(struct clocksource *cs);
void (*disable)(struct clocksource *cs);
unsigned long flags;
void (*suspend)(struct clocksource *cs);
void (*resume)(struct clocksource *cs);
void (*mark_unstable)(struct clocksource *cs);
void (*tick_stable)(struct clocksource *cs);
/* private: */
#ifdef CONFIG_CLOCKSOURCE_WATCHDOG
/* Watchdog related data, used by the framework */
struct list_head wd_list;
u64 cs_last;
u64 wd_last;
#endif
struct module *owner;
};
1.clock_event_device的set_next_event成员函数xxx_timer_set_next_event()
该函数的delta参数是Linux内核传递给底层定时器的一个差值,其含义是下一次节拍中断产生的硬件定时器中计数器的值相对于当前计数器的差值。在该函数中将硬件定时器设置为在“当前计数器计数值+delta”的时刻产生下一次节拍中断。
xxx_clockevent_init()函数中设置可接受的最小和最大delta值对应的纳秒数,即xxx_clockevent.min_delta_ns和xxx_clockevent.max_delta_ns。
2.clocksource的read成员函数xxx_timer_read()
该函数可读取出从开机到当前时刻定时器计数器已经走过的值,无论有没有设置当计数器达到某值时产生中断,硬件的计数总是在进行的(我们要理解,计数总是在进行,而计数到某值后要产生中断则需要软件设置)。因此,该函数给Linux系统提供了一个底层的准确的参考时间。
3.定时器的中断服务程序xxx_timer_interrupt()
在该中断服务程序中,直接调用clock_event_device的event_handler()成员函数,event_handler()成员函数的具体工作是Linux内核根据Linux内核配置和运行情况自行设置的。
4.clock_event_device的set_mode成员函数xxx_timer_set_mode()
用于设置定时器的模式以及恢复、关闭等功能,目前一般采用ONESHOT模式,即一次一次产生中断。新版的Linux也可以使用老的周期性模式,如果内核在编译时没有选择NO_HZ(无节拍),该底层的定时器驱动依然可以为内核的运行提供支持。
这些函数的结合使得ARM Linux内核底层所需要的时钟得以运行。
对于多核处理器,一般的做法是给每个核分配一个独立的定时器,各个核根据自身的运行情况动态地设置自己时钟中断发生的时刻。
20.3 中断控制器驱动
在Linux内核中,各个设备驱动可以调用request_irq()、enable_irq()、disable_irq()、local_irq_disable()、local_irq_enable()等通用API来完成中断申请、使能、禁止等功能。在将Linux移
植到新的SoC时,芯片供应商需要提供该部分API的底层支持。
local_irq_disable()、local_irq_enable()的实现与具体中断控制器无关,对于ARM v6以上的体系结
构,是直接调用CPSID/CPSIE指令进行,对于ARM v6以前的体系结构,则是通过MRS/MSR指令来读取和设置ARM的CPSR寄存器。由此可见,local_irq_disable()、local_irq_enable()针对的并不是外部的中断控制器,而是直接让CPU本身不响应中断请求。相关的实现位于arch/arm/include/asm/irqflags.h,如代码清单20.3所示。
代码清单20.3 ARM Linux local_irq_disable()/enable()底层实现(C内嵌汇编)
#if __LINUX_ARM_ARCH__ >= 6
static inline unsigned long arch_local_irq_save(void)
{undefined
unsigned long flags;
asm volatile(
" mrs %0, " IRQMASK_REG_NAME_R " @ arch_local_irq_save\n"
" cpsid i"
: "=r" (flags) : : "memory", "cc");
return flags;
}
static inline void arch_local_irq_enable(void)
{undefined
asm volatile(
" cpsie i @ arch_local_irq_enable"
:
:
: "memory", "cc");
}
static inline void arch_local_irq_disable(void)
{undefined
asm volatile(
" cpsid i @ arch_local_irq_disable"
:
:
: "memory", "cc");
}
#else
/*
* Save the current interrupt enable state & disable IRQs
*/
static inline unsigned long arch_local_irq_save(void)
{undefined
unsigned long flags, temp;
asm volatile(
" mrs %0, cpsr @ arch_local_irq_save\n"
" orr %1, %0, #128\n"
" msr cpsr_c, %1"
: "=r" (flags), "=r" (temp)
:
: "memory", "cc");
return flags;
}
/*
* Enable IRQs
*/
static inline void arch_local_irq_enable(void)
{undefined
unsigned long temp;
asm volatile(
" mrs %0, cpsr @ arch_local_irq_enable\n"
" bic %0, %0, #128\n"
" msr cpsr_c, %0"
: "=r" (temp)
:
: "memory", "cc");
}
/*
* Disable IRQs
*/
static inline void arch_local_irq_disable(void)
{undefined
unsigned long temp;
asm volatile(
" mrs %0, cpsr @ arch_local_irq_disable\n"
" orr %0, %0, #128\n"
" msr cpsr_c, %0"
: "=r" (temp)
:
: "memory", "cc");
}
#endif
与local_irq_disable()和local_irq_enable()不同,disable_irq()和enable_irq()针对的则是中断控制器,因此disable_irq()和enable_irq()适用的对象是某个中断。disable_irq()的字面意思是暂时屏蔽掉某中断(其实在内核的实现层面上做了延后屏蔽),直到enable_irq()后再执行ISR。实际上,屏蔽中断可以发生在外设、中断控制器、CPU三个位置,如图20.3所示。
对于外设端,是从源头上就不产生中断信号给中断控制器,由于外设端高度依赖于外设于本身,所以Linux不提供标准的API而是由外设的驱动直接读写自身的寄存器。
在内核中,使用irq_chip结构体来描述中断控制器。该结构体内部封装了中断mask、unmask、ack等成员函数,其定义于include/linux/irq.h中,如代码清单20.4所示。
代码清单20.4 中断控制器irq_chip结构体
struct irq_chip {undefined
struct device *parent_device;
const char *name;
unsigned int (*irq_startup)(struct irq_data *data);
void (*irq_shutdown)(struct irq_data *data);
void (*irq_enable)(struct irq_data *data);
void (*irq_disable)(struct irq_data *data);
void (*irq_ack)(struct irq_data *data);// 清
void (*irq_mask)(struct irq_data *data);//中断屏蔽
void (*irq_mask_ack)(struct irq_data *data);//取消中断屏蔽
void (*irq_unmask)(struct irq_data *data);
void (*irq_eoi)(struct irq_data *data);
int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
int (*irq_retrigger)(struct irq_data *data);
int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
int (*irq_set_wake)(struct irq_data *data, unsigned int on);
void (*irq_bus_lock)(struct irq_data *data);
void (*irq_bus_sync_unlock)(struct irq_data *data);
void (*irq_cpu_online)(struct irq_data *data);
void (*irq_cpu_offline)(struct irq_data *data);
void (*irq_suspend)(struct irq_data *data);
void (*irq_resume)(struct irq_data *data);
void (*irq_pm_shutdown)(struct irq_data *data);
void (*irq_calc_mask)(struct irq_data *data);
void (*irq_print_chip)(struct irq_data *data, struct seq_file *p);
int (*irq_request_resources)(struct irq_data *data);
void (*irq_release_resources)(struct irq_data *data);
void (*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg);
void (*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg);
int (*irq_get_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool *state);
int (*irq_set_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool state);
int (*irq_set_vcpu_affinity)(struct irq_data *data, void *vcpu_info);
void (*ipi_send_single)(struct irq_data *data, unsigned int cpu);
void (*ipi_send_mask)(struct irq_data *data, const struct cpumask *dest);
unsigned long flags;
};
备注:
各芯片公司会将芯片内部的中断控制器实现为irq_chip驱动的形式。受限于中断控制器硬件的能力,这些成员函数并不一定需要全部实现,有时只需要实现其中的部分函数即可。如
drivers/pinctrl/sirf/pinctrl-sirf.c驱动中的下面代码部分:
static struct irq_chip sirfsoc_irq_chip = {undefined
.name = "sirf-gpio-irq",
.irq_ack = sirfsoc_gpio_irq_ack, // 清中断
.irq_mask = sirfsoc_gpio_irq_mask,// 中断屏蔽
.irq_unmask = sirfsoc_gpio_irq_unmask,// 取消中断屏蔽
.irq_set_type = sirfsoc_gpio_irq_type,// 设置中断触发方式,如高电平、低电平、上升沿、下降沿等
};
至于到enable_irq()时,虽然没有实现irq_enable()成员函数,但是内核会间接调用irq_unmask()成员函数,这点从kernel/irq/chip.c中可以看出:
void irq_enable(struct irq_desc *desc)
{
irq_state_clr_disabled(desc);
if (desc->irq_data.chip->irq_enable)
desc->irq_data.chip->irq_enable(&desc->irq_data);
else
desc->irq_data.chip-> irq_unmask(&desc->irq_data);
irq_state_clr_masked(desc);
}
在芯片内部,中断控制器可能不止1个,多个中断控制器之间还很可能是级联的。假设芯片内部有一个中断控制器,支持32个中断源,其中有4个来源于GPIO控制器外围的4组GPIO,每组GPIO上又有32个中断(许多芯片的GPIO控制器也同时是一个中断控制器),其关系如图20.4所示。
图20.4 SoC中断控制器的典型分布
一般来讲,在实际操作中,gpio0_0~gpio0_31这些引脚本身在第1级会使用中断号28,而这些引脚本身的中断号在实现与GPIO控制器对应的irq_chip驱动时,我们又会把它映射到Linux系统的32~63号中断。同理,gpio1_0~gpio1_31这些引脚本身在第1级会使用中断号29,而这些引脚本身的中断号在实现与GPIO控制器对应的irq_chip驱动时,我们又会把它映射到Linux系统的64~95号中断,以此类推。对于中断号的使用者而言,无须看到这种2级映射关系。如果某设备想申请与gpio1_0这个引脚对应的中断,这个设备只需要申请64号中断即可。这个关系图看起来如图20.5所示。
图20.5 中断级联与映射
特别注意,上述图20.4和20.5中所涉及的中断号的数值,无论是base还是具体某个GPIO对应的中断号是多少,都不一定是如图20.4和图20.5所描述的简单线性映射。Linux使用IRQ Domain来描述一个中断控制器所管理的中断源。每个中断控制器都有自己的Domain。可以将IRQ Domain看作是IRQ控制器的软件抽象。在添加IRQ Domain的时候,内核中存在的映射方法有:irq_domain_add_legacy()、irq_domain_add_linear()、irq_domain_add_tree()等。
irq_domain_add_legacy()是一种过时的方法,irq_domain_add_legacy()一般是由IRQ控制器驱动直接指定中断源硬件意义上的偏移(一般称为hwirq)和Linux逻辑上的中断号的映射关系。
irq_domain_add_linear()则在中断源和irq_desc之间建立线性映射,内核针对这个IRQ Domain维护了一个hwirq和Linux逻辑IRQ之间关系的一个表,这个时候其实也完全不关心逻辑中断号了。
irq_domain_add_tree()则更加灵活,逻辑中断号和hwirq之间的映射关系是用一棵radix树来描述的,需要通过查找的方法来寻找hwirq和Linux逻辑IRQ之间的关系,一般适合某中断控制器支持非常多中断源的情况。
在当前的内核中,中断号更多的是一个逻辑概念,具体数值是多少不是很关键。更多的是关心在设备树中设置正确的interrupt_parrent和相对该interrupt_parent的偏移。
以drivers/pinctrl/sirf/pinctrl-sirf.c的irq_chip部分为例,在sirfsoc_gpio_probe()函数中,每组GPIO的中
断都通过gpiochip_set_chained_irqchip()级联到上一级中断控制器的中断。
代码清单20.5 二级GPIO中断级联到一级中断控制器
static int sirfsoc_gpio_probe(struct device_node *np)
{undefined
int i, err = 0;
static struct sirfsoc_gpio_chip *sgpio;
struct sirfsoc_gpio_bank *bank;
void __iomem *regs;
struct platform_device *pdev;
u32 pullups[SIRFSOC_GPIO_NO_OF_BANKS], pulldowns[SIRFSOC_GPIO_NO_OF_BANKS];
pdev = of_find_device_by_node(np);
if (!pdev)
return -ENODEV;
sgpio = devm_kzalloc(&pdev->dev, sizeof(*sgpio), GFP_KERNEL);
if (!sgpio)
return -ENOMEM;
spin_lock_init(&sgpio->lock);
regs = of_iomap(np, 0);
if (!regs)
return -ENOMEM;
sgpio->chip.gc.request = sirfsoc_gpio_request;
sgpio->chip.gc.free = sirfsoc_gpio_free;
sgpio->chip.gc.direction_input = sirfsoc_gpio_direction_input;
sgpio->chip.gc.get = sirfsoc_gpio_get_value;
sgpio->chip.gc.direction_output = sirfsoc_gpio_direction_output;
sgpio->chip.gc.set = sirfsoc_gpio_set_value;
sgpio->chip.gc.base = 0;
sgpio->chip.gc.ngpio = SIRFSOC_GPIO_BANK_SIZE * SIRFSOC_GPIO_NO_OF_BANKS;
sgpio->chip.gc.label = kasprintf(GFP_KERNEL, "%pOF", np);
sgpio->chip.gc.of_node = np;
sgpio->chip.gc.of_xlate = sirfsoc_gpio_of_xlate;
sgpio->chip.gc.of_gpio_n_cells = 2;
sgpio->chip.gc.parent = &pdev->dev;
sgpio->chip.regs = regs;
err = gpiochip_add_data(&sgpio->chip.gc, sgpio);
if (err) {undefined
dev_err(&pdev->dev, "%pOF: error in probe function with status %d\n",
np, err);
goto out;
}
err = gpiochip_irqchip_add(&sgpio->chip.gc,
&sirfsoc_irq_chip,
0, handle_level_irq,
IRQ_TYPE_NONE);
if (err) {undefined
dev_err(&pdev->dev,
"could not connect irqchip to gpiochip\n");
goto out_banks;
}
for (i = 0; i < SIRFSOC_GPIO_NO_OF_BANKS; i++) {undefined
bank = &sgpio->sgpio_bank[i];
spin_lock_init(&bank->lock);
bank->parent_irq = platform_get_irq(pdev, i);
if (bank->parent_irq < 0) {undefined
err = bank->parent_irq;
goto out_banks;
}
gpiochip_set_chained_irqchip(&sgpio->chip.gc,&sirfsoc_irq_chip,
bank->parent_irq,// 与这一组GPIO对应的“上级”中断号
sirfsoc_gpio_handle_irq//与bank->parent_irq对应的“上级”中断服务程序
);
}
err = gpiochip_add_pin_range(&sgpio->chip.gc, dev_name(&pdev->dev),
0, 0, SIRFSOC_GPIO_BANK_SIZE * SIRFSOC_GPIO_NO_OF_BANKS);
if (err) {
dev_err(&pdev->dev,
"could not add gpiochip pin range\n");
goto out_no_range;
}
if (!of_property_read_u32_array(np, "sirf,pullups", pullups,
SIRFSOC_GPIO_NO_OF_BANKS))
sirfsoc_gpio_set_pullup(sgpio, pullups);
if (!of_property_read_u32_array(np, "sirf,pulldowns", pulldowns,
SIRFSOC_GPIO_NO_OF_BANKS))
sirfsoc_gpio_set_pulldown(sgpio, pulldowns);
return 0;
out_no_range:
out_banks:
gpiochip_remove(&sgpio->chip.gc);
out:
iounmap(regs);
return err;
}
在sirfsoc_gpio_handle_irq()函数的入口处调用chained_irq_enter()暗示自身进入链式IRQ处理,在函数体内判断具体的GPIO中断,并通过generic_handle_irq()调用最终的外设驱动中的中断服务程序,最后调用chained_irq_exit()暗示自身退出链式IRQ处理,如代码清单20.6所示。
代码清单20.6 “上级”中断服务程序派生到下级
static void sirfsoc_gpio_handle_irq(struct irq_desc *desc)
{undefined
unsigned int irq = irq_desc_get_irq(desc);
struct gpio_chip *gc = irq_desc_get_handler_data(desc);
struct sirfsoc_gpio_chip *sgpio = gpiochip_get_data(gc);
struct sirfsoc_gpio_bank *bank;
u32 status, ctrl;
int idx = 0;
struct irq_chip *chip = irq_desc_get_chip(desc);
int i;
for (i = 0; i < SIRFSOC_GPIO_NO_OF_BANKS; i++) {undefined
bank = &sgpio->sgpio_bank[i];
if (bank->parent_irq == irq)
break;
}
BUG_ON(i == SIRFSOC_GPIO_NO_OF_BANKS);
chained_irq_enter(chip, desc);//进入链式IRQ处理
status = readl(sgpio->chip.regs + SIRFSOC_GPIO_INT_STATUS(bank->id));
if (!status) {undefined
printk(KERN_WARNING
"%s: gpio id %d status %#x no interrupt is flagged\n",
__func__, bank->id, status);
handle_bad_irq(desc);
return;
}
while (status) {undefined
ctrl = readl(sgpio->chip.regs + SIRFSOC_GPIO_CTRL(bank->id, idx));
/*
* Here we must check whether the corresponding GPIO's interrupt
* has been enabled, otherwise just skip it
*/
if ((status & 0x1) && (ctrl & SIRFSOC_GPIO_CTL_INTR_EN_MASK)) {undefined
pr_debug("%s: gpio id %d idx %d happens\n",
__func__, bank->id, idx);
generic_handle_irq(irq_find_mapping(gc->irq.domain, idx +
bank->id * SIRFSOC_GPIO_BANK_SIZE));//调用最终的外设驱动中的中断服务程序
}
idx++;
status = status >> 1;
}
chained_irq_exit(chip, desc);//退出链式IRQ处理
}
用一个实例来呈现这个过程,假设GPIO0_0~31对应上级中断号28,而外设A使用了GPIO0_5(即第0组GPIO的第5个),并假定外设A的中断号为37,即32+5,中断服务程序为deva_isr()。那么,当GPIO0_5中断发生时,内核的调用顺序是:sirfsoc_gpio_handle_irq()->generic_handle_irq()->deva_isr()。如果硬件的中断系统有更深的层次,这种软件上的中断服务程序级联实际上可以有更深的级别。
在上述实例中,GPIO0_0~31的interrupt_parrent是上级中断控制器,而外设A的interrupt_parrent就
是GPIO0,这些都会在设备树中呈现。
很多中断控制器的寄存器定义呈现出简单的规律,如有一个mask寄存器,其中每1位可屏蔽1个中断等,在这种情况下,无须实现1个完整的irq_chip驱动,而可以使用内核提供的通用irq_chip驱动架构irq_chip_generic,这样只需要实现极少量的代码,如drivers/irqchip/irq-sirfsoc.c中,用于注册CSR SiRFprimaII内部中断控制器的代码(见代码清单20.7)。
代码清单20.7 使用generic的irq_chip(中断控制器)框架
static __init void sirfsoc_alloc_gc(void __iomem *base)
{undefined
unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
unsigned int set = IRQ_LEVEL;
struct irq_chip_generic *gc;
struct irq_chip_type *ct;
int i;
irq_alloc_domain_generic_chips(sirfsoc_irqdomain, 32, 1, "irq_sirfsoc",
handle_level_irq, clr, set,
IRQ_GC_INIT_MASK_CACHE);
for (i = 0; i < SIRFSOC_NUM_BANKS; i++) {undefined
gc = irq_get_domain_generic_chip(sirfsoc_irqdomain, i * 32);
gc->reg_base = base + i * SIRFSOC_INT_BASE_OFFSET;
ct = gc->chip_types;
ct->chip.irq_mask = irq_gc_mask_clr_bit;
ct->chip.irq_unmask = irq_gc_mask_set_bit;
ct->regs.mask = SIRFSOC_INT_RISC_MASK0;
}
}
irq_chip驱动的入口声明方法形如:
static int __init sirfsoc_irq_init(struct device_node *np,
struct device_node *parent)
{undefined
void __iomem *base = of_iomap(np, 0);
if (!base)
panic("unable to map intc cpu registers\n");
sirfsoc_irqdomain = irq_domain_add_linear(np, SIRFSOC_NUM_IRQS,
&irq_generic_chip_ops, base);
sirfsoc_alloc_gc(base);
writel_relaxed(0, base + SIRFSOC_INT_RISC_LEVEL0);
writel_relaxed(0, base + SIRFSOC_INT_RISC_LEVEL1);
writel_relaxed(0, base + SIRFSOC_INT_RISC_MASK0);
writel_relaxed(0, base + SIRFSOC_INT_RISC_MASK1);
set_handle_irq(sirfsoc_handle_irq);
return 0;
}
IRQCHIP_DECLARE(sirfsoc_intc, "sirf,prima2-intc", sirfsoc_irq_init);
sirf,prima2-intc是设备树中中断控制器的compatible字段,sirfsoc_irq_init是匹配这个compatible字段后运行的初始化函数。
目前多数主流ARM芯片内部的一级中断控制器都使用了ARM公司的GIC,我们几乎不需要实现任何代码,只需要在设备树中添加相关的节点。
如在arch/arm/boot/dts/exynos5250.dtsi中即含有:
gic:interrupt-controller@10481000 {undefined
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x10481000 0x1000>, <0x10482000 0x2000>;
};
在drivers/irqchip/irq-gic.c,发现GIC驱动的入口声明如下:
IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init);
IRQCHIP_DECLARE(arm11mp_gic, "arm,arm11mp-gic", gic_of_init);
IRQCHIP_DECLARE(arm1176jzf_dc_gic, "arm,arm1176jzf-devchip-gic", gic_of_init);
这说明drivers/irqchip/irq-gic.c这个驱动可以兼容arm,gic-400、arm,arm11mp-gic、arm,arm1176jzf-devchip-gic等,但是初始化函数都是统一的gic_of_init。
include/linux/of.h
#define OF_DECLARE_2(table, name, compat, fn) \
_OF_DECLARE(table, name, compat, fn, of_init_fn_2)
include/linux/irqchip.h
/*
* This macro must be used by the different irqchip drivers to declare
* the association between their DT compatible string and their
* initialization function.
*
* @name: name that must be unique accross all IRQCHIP_DECLARE of the
* same file.
* @compstr: compatible string of the irqchip driver
* @fn: initialization function
*/
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
20.4 SMP多核启动以及CPU热插拔驱动
在Linux系统中,对于多核的ARM芯片,在Bootrom代码中,每个CPU都会识别自身ID,如果ID是0,则引导Bootloader和Linux内核执行,如果ID不是0,则Bootrom一般在上电时将自身置于WFI(WaitFortInerrupt)或者WFE(WaitForEvent)状态,并等待CPU0给其发CPU核间中断或事件(一般通过SEV指令)以唤醒它。一个典型的多核Linux启动过程如图20.6所示。
图20.6 一个典型的多核Linux启动过程
被CPU0唤醒的CPUn可以在运行过程中进行热插拔,如运行如下命令即可卸载CPU1,并且将CPU1上的任务全部迁移到其他CPU中:
echo 0 > /sys/devices/system/cpu/cpu1/online
同理,运行如下命令可以再次启动CPU1:
echo 1 > /sys/devices/system/cpu/cpu1/online
之后CPU1会主动参与系统中各个CPU之间要运行任务的负载均衡工作。
CPU0唤醒其他CPU的动作在内核中被封装为一个smp_operations的结构体,对于ARM而言,定义于
arch/arm/include/asm/smp.h中。该结构体的成员函数如代码清单20.8所示。
代码清单20.8 smp_operations结构体
struct smp_operations {undefined
#ifdef CONFIG_SMP
/*
* Setup the set of possible CPUs (via set_cpu_possible)
*/
void (*smp_init_cpus)(void);
/*
* Initialize cpu_possible map, and enable coherency
*/
void (*smp_prepare_cpus)(unsigned int max_cpus);
/*
* Perform platform specific initialisation of the specified CPU.
*/
void (*smp_secondary_init)(unsigned int cpu);
/*
* Boot a secondary CPU, and assign it the specified idle task.
* This also gives us the initial stack to use for this CPU.
*/
int (*smp_boot_secondary)(unsigned int cpu, struct task_struct *idle);
#ifdef CONFIG_HOTPLUG_CPU
int (*cpu_kill)(unsigned int cpu);
void (*cpu_die)(unsigned int cpu);
bool (*cpu_can_disable)(unsigned int cpu);
int (*cpu_disable)(unsigned int cpu);
#endif
#endif
};
从arch/arm/mach-vexpress/v2m.c中看到VEXPRESS电路板用到的smp_ops()为vexpress_smp_ops:
DT_MACHINE_START(VEXPRESS_DT, "ARM-Versatile Express")
.dt_compat = v2m_dt_match,
.smp = smp_ops(vexpress_smp_ops),
.map_io = v2m_dt_map_io,
…
MACHINE_END
通过arch/arm/mach-vexpress/platsmp.c的实现代码可以看出,smp_operations的
成员函数smp_init_cpus(),即vexpress_smp_init_cpus()调用的ct_ca9x4_init_cpu_map()会探测SoC内CPU核的个数,并通过set_cpu_possible()设置这些CPU可见。
而smp_operations的成员函数smp_prepare_cpus(),即vexpress_smp_prepare_cpus()则会通过
v2m_flags_set(virt_to_phys(versatile_secondary_startup))设置其他CPU的启动地址为
versatile_secondary_startup,如代码清单20.9所示。
代码清单20.9 在smp_prepare_cpus()中设置CPU1...n的启动地址
static void __init vexpress_smp_prepare_cpus(unsigned int max_cpus)
{undefined
…
/*
* Write the address of secondary startup into the
* system-wide flags register. The boot monitor waits
* until it receives a soft interrupt, and then the
* secondary CPU branches to this address.
*/
v2m_flags_set(virt_to_phys(versatile_secondary_startup));
}
这部分的具体实现方法是与SoC相关的,由芯片的设计以及芯片内部的Bootrom决定。对于VEXPRESS来讲,设置方法如下:
void __init v2m_flags_set(u32 data)
{undefined
writel(~0, v2m_sysreg_base + V2M_SYS_FLAGSCLR);
writel(data, v2m_sysreg_base + V2M_SYS_FLAGSSET);
}
即填充v2m_sysreg_base+V2M_SYS_FLAGSCLR标记清除寄存器为0xFFFFFFFF,将CPU1...n初始启动执行的指令地址填入v2m_sysreg_base+V2M_SYS_FLAGSSET寄存器。这两个地址由芯片实现时内部的Bootrom程序设定的。填入CPU1...n的起始地址都通过virt_to_phys()转化为物理地址,因为此时CPU1...n的MMU尚未开启。
比较关键的是smp_operations的成员函数smp_boot_secondary(),对于本例为
versatile_boot_secondary(),它完成CPU的最终唤醒工作,如代码清单20.10所示。
代码清单20.10 CPU0通过中断唤醒其他CPU
static void write_pen_release(int val)
{undefined
pen_release = val;
smp_wmb();
sync_cache_w(&pen_release);
}
int versatile_boot_secondary(unsigned int cpu, struct task_struct *idle)
{undefined
unsigned long timeout;
...
/*
* This is really belt and braces; we hold unintended secondary
* CPUs in the holding pen until we ’ re ready for them. However,
* since we haven ’ t sent them a soft interrupt, they shouldn ’ t
* be there.
*/
write_pen_release(cpu_logical_map(cpu));//将pen_release变量设置为要唤醒的CPU核的CPU号
cpu_logical_map(cpu),
/*
* Send the secondary CPU a soft interrupt, thereby causing
* the boot monitor to read the system wide flags register,
* and branch to the address found there.
*/
arch_send_wakeup_ipi_mask(cpumask_of(cpu));//给要唤醒的CPU发IPI中断
timeout = jiffies + (1 * HZ);
while (time_before(jiffies, timeout)) {undefined
smp_rmb();
if (pen_release == -1)
break;
udelay(10);
}
...
return pen_release != -1 -ENOSYS : 0;
}
versatile_secondary_startup实现于arch/arm/plat-versatile/headsmp.S中,是汇编,如代码清单20.11所示。
代码清单20.11 被唤醒CPU的执行入口
1ENTRY(versatile_secondary_startup)
2 mrc p15, 0, r0, c0, c0, 5
3 and r0, r0, #15
4 adr r4, 1f
5 ldmia r4, {r5, r6}
6 sub r4, r4, r5
7 add r6, r6, r4
8pen: ldr r7, [r6]
9 cmp r7, r0
10 bne pen
11
12 /*
13 * we ’ ve been released from the holding pen: secondary_stack
14 * should now contain the SVC stack for this core
15 */
16 b secondary_startup
17
18 .align
191: .long .
20 .long pen_release
21ENDPROC(versatile_secondary_startup)
分析:
上述代码第8~10行的循环是等待pen_release变量成为CPU0设置的cpu_logical_map(cpu),一般直接
就成立了。第16行则调用内核通用的secondary_startup()函数,经过一系列的初始化(如MMU等),最
终新的被唤醒的CPU将调用smp_operations的smp_secondary_init()成员函数,对于本例为
versatile_secondary_init(),如代码清单20.12所示。
代码清单20.12 被唤醒的CPU恢复pen_release()
void versatile_secondary_init(unsigned int cpu)
{undefined
/*
* let the primary processor know we ’ re out of the
* pen, then head off into the C entry point
*/
write_pen_release(-1);
/*
* Synchronise with the boot thread.
*/
spin_lock(&boot_lock);
spin_unlock(&boot_lock);
}
上述代码第会将pen_release写为-1,于是CPU0还在执行的代码清单20.10里versatile_boot_secondary()函数中的如下循环就退出了:
while (time_before(jiffies, timeout)) {undefined
smp_rmb();
if (pen_release == -1)
break;
udelay(10);
}
这样CPU0就知道目标CPU已经被正确地唤醒,此后CPU0和新唤醒的其他CPU各自运行。整个系统在运行过程中会进行实时进程和正常进程的动态负载均衡。
图20.7描述上文提到的vexpress_smp_prepare_cpus()、versatile_boot_secondary()、
write_pen_release()、versatile_secondary_startup()、versatile_secondary_init()等函数的执行顺序。
图20.7 CPU0唤醒其他CPU过程
CPU热插拔的实现是与芯片相关的,对于VEXPRESS,实现了smp_operations的cpu_die()成员函数,即vexpress_cpu_die()。它会在进行CPUn的拔除操作时将CPUn投入低功耗的WFI状态,相关代码位于arch/arm/mach-vexpress/hotplug.c中,如代码清单20.13所示。
代码清单20.13 smp_operations的cpu_die()成员函数
static inline void platform_do_lowpower(unsigned int cpu, int *spurious)
{undefined
/*
* there is no power-control hardware on this platform, so all
* we can do is put the core into WFI; this is safe as the calling
* code will have already disabled interrupts
*/
for (;;) {undefined
wfi();
if (pen_release == cpu_logical_map(cpu)) {undefined
/*
* OK, proper wakeup, we're done
*/
break;
}
/*
* Getting here, means that we have come out of WFI without
* having been woken up - this shouldn't happen
*
* Just note it happening - when we're woken, we can report
* its occurrence.
*/
(*spurious)++;
}
}
/*
* platform-specific code to shutdown a CPU
*
* Called with IRQs disabled
*/
void vexpress_cpu_die(unsigned int cpu)
{undefined
int spurious = 0;
/*
* we're ready for shutdown now, so do it
*/
cpu_enter_lowpower();
platform_do_lowpower(cpu, &spurious);
/*
* bring this CPU back into the world of cache
* coherency, and then restore interrupts
*/
cpu_leave_lowpower();
if (spurious)
pr_warn("CPU%u: %u spurious wakeup calls\n", cpu, spurious);
}
分析:
CPUn睡眠于wfi(),之后再次在线的时候,又会因为CPU0给它发出的IPI而从wfi()函数返回继续执行,醒来时CPUn也判断“pen_release==cpu_logical_map(cpu)”是否成立,以确定该次醒来确实是由
CPU0唤醒的一次正常醒来。
20.6 GPIO驱动
在drivers/gpio下实现了通用的基于gpiolib的GPIO驱动,其中定义了一个通用的用于描述底层GPIO控制器的gpio_chip结构体,并要求具体的SoC实现gpio_chip结构体的成员函数,最后通过gpiochip_add()注册gpio_chip。GPIO驱动可以存在于drivers/gpio目录中,但是在GPIO兼有多种功能且需要复杂配置的情况下,GPIO的驱动部分往往直接移到drivers/pinctrl目录下并连同pinmux一起实现,而不存在于drivers/gpio目录中。
GPIO控制器的gpio_chip结构体封装了底层硬件的GPIO enable()/disable()等操作,其定义如代码清单20.15所示。
代码清单20.15 描述GPIO控制器的gpio_chip结构体
include/linux/gpio/driver.h
struct gpio_chip {
const char *label;
struct gpio_device *gpiodev;
struct device *parent;
struct module *owner;
int (*request)(struct gpio_chip *chip, unsigned offset);
void (*free)(struct gpio_chip *chip, unsigned offset);
int (*get_direction)(struct gpio_chip *chip, unsigned offset);
int (*direction_input)(struct gpio_chip *chip, unsigned offset);
int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value);
int (*get)(struct gpio_chip *chip, unsigned offset);
int (*get_multiple)(struct gpio_chip *chip, unsigned long *mask, unsigned long *bits);
void (*set)(struct gpio_chip *chip, unsigned offset, int value);
void (*set_multiple)(struct gpio_chip *chip, unsigned long *mask, unsigned long *bits);
int (*set_config)(struct gpio_chip *chip, unsigned offset, unsigned long config);
int (*to_irq)(struct gpio_chip *chip, unsigned offset);
void (*dbg_show)(struct seq_file *s, struct gpio_chip *chip);
int base;
u16 ngpio;
const char *const *names;
bool can_sleep;
#if IS_ENABLED(CONFIG_GPIO_GENERIC)
unsigned long (*read_reg)(void __iomem *reg);
void (*write_reg)(void __iomem *reg, unsigned long data);
bool be_bits;
void __iomem *reg_dat;
void __iomem *reg_set;
void __iomem *reg_clr;
void __iomem *reg_dir;
int bgpio_bits;
spinlock_t bgpio_lock;
unsigned long bgpio_data;
unsigned long bgpio_dir;
#endif
#ifdef CONFIG_GPIOLIB_IRQCHIP
/*
* With CONFIG_GPIOLIB_IRQCHIP we get an irqchip inside the gpiolib
* to handle IRQs for most practical cases.
*/
/**
* @irq:
*
* Integrates interrupt chip functionality with the GPIO chip. Can be
* used to handle IRQs for most practical cases.
*/
struct gpio_irq_chip irq;
#endif
#if defined(CONFIG_OF_GPIO)
/*
* If CONFIG_OF is enabled, then all GPIO controllers described in the
* device tree automatically may have an OF translation
*/
/**
* @of_node:
*
* Pointer to a device tree node representing this GPIO controller.
*/
struct device_node *of_node;
/**
* @of_gpio_n_cells:
*
* Number of cells used to form the GPIO specifier.
*/
unsigned int of_gpio_n_cells;
/**
* @of_xlate:
*
* Callback to translate a device tree GPIO specifier into a chip-
* relative GPIO number and flags.
*/
int (*of_xlate)(struct gpio_chip *gc, const struct of_phandle_args *gpiospec, u32 *flags);
#endif
};
通过这层封装,每个具体的要用到GPIO的设备驱动都使用通用的GPIO API来操作GPIO,这些API主要用于GPIO的申请、释放和设置:
asm-generic/gpio.h
int gpio_request(unsigned gpio, const char *label);
void gpio_free(unsigned gpio);
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
int gpio_set_debounce(unsigned gpio, unsigned debounce);
int gpio_get_value_cansleep(unsigned gpio);
void gpio_set_value_cansleep(unsigned gpio, int value);
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);
int gpio_request_array(const struct gpio *array, size_t num);
void gpio_free_array(const struct gpio *array, size_t num);
int devm_gpio_request(struct device *dev, unsigned gpio, const char *label);
int devm_gpio_request_one(struct device *dev, unsigned gpio,
unsigned long flags, const char *label);
void devm_gpio_free(struct device *dev, unsigned int gpio);
注意:
内核中针对内存、IRQ、时钟、GPIO、pinctrl、Regulator都有以devm_开头的API,使用这部分API的时候,内核会有类似于Java的资源自动回收机制,在代码中进行出错处理时,无须释放相关的资源。
对于GPIO,内核会创建/sys节点/sys/class/gpio/gpioN/,通过这个节点可以echo值来改变GPIO的方向、设置并获取GPIO的值。
在拥有设备树支持的情况下,可以通过设备树来描述某GPIO控制器提供的GPIO引脚被具体设备使用的情况。在GPIO控制器对应的节点中,需定义#gpio-cells和gpio-controller属性,具体的设备节点则通过xxx-gpios属性来引用GPIO控制器节点及GPIO引脚。
如VEXPRESS电路板DT文件arch/arm/boot/dts/vexpress-v2m.dtsi中有如下GPIO控制器节点:
v2m_sysreg: sysreg@00000 {
compatible = "arm,vexpress-sysreg";
reg = <0x00000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
};
VEXPRESS电路板上的MMC控制器会使用该节点GPIO控制器提供的GPIO引脚,则具体的mmci@05000设备节点会通过-gpios属性引用GPIO:
mmci@05000 {
compatible = "arm,pl180", "arm,primecell";
reg = <0x05000 0x1000>;
interrupts = <9 10>;
cd-gpios = <&v2m_sysreg 0 0>;
wp-gpios = <&v2m_sysreg 1 0>;
…
};
备注:
其中cd-gpios用于SD/MMC卡的探测,而wp-gpios用于写保护,MMC主机控制器驱动会通过如下方法获取这两个GPIO,见于drivers/mmc/host/mmci.c:
static void mmci_dt_populate_generic_pdata(struct device_node *np, struct mmci_platform_data *pdata)
{
...
pdata->gpio_wp = of_get_named_gpio(np, "wp-gpios", 0);
pdata->gpio_cd = of_get_named_gpio(np, "cd-gpios", 0);
...
}
20.7 pinctrl驱动
许多SoC内部都包含pin(管脚/引脚)控制器,通过pin控制器的寄存器,可以配置一个或者一组引脚的功能和
特性。在软件上,Linux内核的pinctrl驱动可以操作pin控制器完成如下工作:
枚举并且命名pin控制器可控制的所有引脚;
提供引脚复用的能力;
提供配置引脚的能力,如驱动能力、上拉下拉、开漏(Open Drain)等。
1.pinctrl和引脚
在特定SoC的pinctrl驱动中,需要定义引脚。假设有一个PGA(Pin Grid Array Package 插针网格阵列封装技术)封装的芯片的引脚排布如图20.9所示。
图20.9 一个PGA封装的芯片的引脚排布
在pinctrl驱动初始化时,向pinctrl子系统注册一个pinctrl_desc描述符,该描述符的pins成员中包含所有引脚的列表。通过代码清单20.16的方法来注册这个pin控制器并命名它的所有引脚。
代码清单20.16 pinctrl引脚描述
Documentation/driver-api/pinctl.rst
#include <linux/pinctrl/pinctrl.h>
const struct pinctrl_pin_desc foo_pins[] = {
PINCTRL_PIN(0, "A8"),
PINCTRL_PIN(1, "B8"),
PINCTRL_PIN(2, "C8"),
...
PINCTRL_PIN(61, "F1"),
PINCTRL_PIN(62, "G1"),
PINCTRL_PIN(63, "H1"),
};
static struct pinctrl_desc foo_desc = {
.name = "foo",
.pins = foo_pins,
.npins = ARRAY_SIZE(foo_pins),
.owner = THIS_MODULE,
};
int __init foo_probe(void)
{
int error;
struct pinctrl_dev *pctl;
error = pinctrl_register_and_init(&foo_desc, <PARENT>, NULL, &pctl);
if (error)
return error;
return pinctrl_enable(pctl);
}
2.引脚组(Pin Group)
在pinctrl子系统中,支持将一组引脚绑定为同一功能。假设{0,8,16,24}这一组引脚承担SPI的功能,而{24,25}这一组引脚承担I 2 C接口功能。在驱动代码中,需体现这个分组关系,并且为这些分组实现pinctrl_ops的成员函数get_groups_count()、get_group_name()和get_group_pins(),将pinctrl_ops填充到前文pinctrl_desc的实例foo_desc中,如代码清单20.17所示。
代码清单20.17 pinctrl驱动对引脚分组
#include <linux/pinctrl/pinctrl.h>
struct foo_group {
const char *name;
const unsigned int *pins;
const unsigned num_pins;
};
static const unsigned int spi0_pins[] = { 0, 8, 16, 24 };
static const unsigned int i2c0_pins[] = { 24, 25 };
static const struct foo_group foo_groups[] = {
{
.name = "spi0_grp",
.pins = spi0_pins,
.num_pins = ARRAY_SIZE(spi0_pins),
},
{
.name = "i2c0_grp",
.pins = i2c0_pins,
.num_pins = ARRAY_SIZE(i2c0_pins),
},
};
static int foo_get_groups_count(struct pinctrl_dev *pctldev)
{
return ARRAY_SIZE(foo_groups);
}
static const char *foo_get_group_name(struct pinctrl_dev *pctldev, unsigned selector)
{
return foo_groups[selector].name;
}
static int foo_get_group_pins(struct pinctrl_dev *pctldev, unsigned selector,
const unsigned **pins,
unsigned *num_pins)
{
*pins = (unsigned *) foo_groups[selector].pins;
*num_pins = foo_groups[selector].num_pins;
return 0;
}
static struct pinctrl_ops foo_pctrl_ops = {
.get_groups_count = foo_get_groups_count,
.get_group_name = foo_get_group_name,
.get_group_pins = foo_get_group_pins,
};
static struct pinctrl_desc foo_desc = {
...
.pctlops = &foo_pctrl_ops,
};
分析:
get_groups_count():用于告知pinctrl子系统该SoC中合法的被选引脚组有多少个
get_group_name():提供引脚组的名字
get_group_pins():提供引脚组的引脚表
备注:
在设备驱动调用pinctrl通用API使能某一组引脚的对应功能时,pinctrl子系统的核心层会调用上述的回调函数。
3.引脚配置
设备驱动有时要配置引脚,譬如可能把引脚设置为高阻或者三态,或通过某阻值将引脚上拉/下拉以确保默认状态下引脚的电平状态。在驱动中可自定义相应板级引脚配置API的细节,譬如某设备驱动可能通过如下代码将某引脚上拉:
#include <linux/pinctrl/consumer.h>
ret = pin_config_set("foo-dev", "FOO_GPIO_PIN", PLATFORM_X_PULL_UP);
其中的PLATFORM_X_PULL_UP由特定的pinctrl驱动定义。在特定的pinctrl驱动中,需要实现完成这些配置所需要的回调函数(pinctrl_desc的confops成员函数),如代码清单20.18所示。
代码清单20.18 引脚的配置
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinconf.h>
#include "platform_x_pindefs.h"
static int foo_pin_config_get(struct pinctrl_dev *pctldev,
unsigned offset,
unsigned long *config)
{
struct my_conftype conf;
... Find setting for pin @ offset ...
*config = (unsigned long) conf;
}
static int foo_pin_config_set(struct pinctrl_dev *pctldev,
unsigned offset,
unsigned long config)
{
struct my_conftype *conf = (struct my_conftype *) config;
switch (conf) {
case PLATFORM_X_PULL_UP:
...
}
}
}
static int foo_pin_config_group_get (struct pinctrl_dev *pctldev,
unsigned selector,
unsigned long *config)
{
...
}
static int foo_pin_config_group_set (struct pinctrl_dev *pctldev,
unsigned selector,
unsigned long config)
{
...
}
static struct pinconf_ops foo_pconf_ops = {
.pin_config_get = foo_pin_config_get,
.pin_config_set = foo_pin_config_set,
.pin_config_group_get = foo_pin_config_group_get,
.pin_config_group_set = foo_pin_config_group_set,
};
/* Pin config operations are handled by some pin controller */
static struct pinctrl_desc foo_desc = {
...
.confops = &foo_pconf_ops,
};
分析:
pin_config_group_get()、pin_config_group_set()针对的是可同时配置一个引脚组的状态情况
pin_config_get()、pin_config_set()针对的是单个引脚的配置
4.与GPIO子系统的交互
pinctrl驱动所覆盖的引脚可同时作为GPIO用,内核的GPIO子系统和pinctrl子系统本来是并行工作的,但有时需要交叉映射,在这种情况下,需要在pinctrl驱动中告知pinctrl子系统核心层GPIO与底层pinctrl驱动所管理的引脚之间的映射关系。假设pinctrl驱动中定义的引脚32~47与gpio_chip实例chip_a的GPIO对应,引脚64~71与gpio_chip实例chip_b的GPIO对应,即映射关系为:
chip a:
- GPIO range : [32 .. 47]
- pin range : [32 .. 47]
chip b:
- GPIO range : [48 .. 55]
- pin range : [64 .. 71]
在特定pinctrl驱动中可以通过如下代码注册两个GPIO范围,如代码清单20.19所示。
代码清单20.19 GPIO与pinctrl引脚的映射
struct gpio_chip chip_a;
struct gpio_chip chip_b;
static struct pinctrl_gpio_range gpio_range_a = {
.name = "chip a",
.id = 0,
.base = 32,
.pin_base = 32,
.npins = 16,
.gc = &chip_a;
};
static struct pinctrl_gpio_range gpio_range_b = {
.name = "chip b",
.id = 0,
.base = 48,
.pin_base = 64,
.npins = 8,
.gc = &chip_b;
};
{
struct pinctrl_dev *pctl;
...
pinctrl_add_gpio_range(pctl, &gpio_range_a);
pinctrl_add_gpio_range(pctl, &gpio_range_b);
}
在基于内核gpiolib的GPIO驱动中,若设备驱动需进行GPIO申请gpio_request()和释放gpio_free(),GPIO驱动则会调用pinctrl子系统中的pinctrl_request_gpio()和pinctrl_free_gpio()通用API,pinctrl子系统会查找申请的GPIO和引脚的映射关系,并确认引脚是否被其他复用功能所占用。与pinctrl子系统通用层pinctrl_request_gpio()和pinctrl_free_gpio()API对应,在底层的具体pinctrl驱动中,需要实现pinmux_ops结构体的gpio_request_enable()和gpio_disable_free()成员函数。
除了gpio_request_enable()和gpio_disable_free()成员函数外,pinmux_ops结构体主要还用来封装
pinmux(引脚复用)功能使能/禁止的回调函数。
5.引脚复用(pinmux)
在pinctrl驱动中可处理引脚复用,它定义了功能(FUNCTIONS),驱动可以设置某功能的使能或者禁止。各个功能联合起来组成一个一维数组,譬如{spi0,i2c0,mmc0}就描述了3个不同的功能。
一个特定的功能总是要求由一些引脚组来完成,引脚组的数量可以为1个或者多个。假设对前文所描述的PGA封装的SoC,引脚分组如图20.10所示。
图20.10 针对PGA封装的SoC的引脚分组
假设I 2 C功能由{A5,B5}引脚组成,而在定义引脚描述的pinctrl_pin_desc结构体实例foo_pins时,将它们的序号定义为了{24,25};而SPI功能则可以由{A8,A7,A6,A5}和{G4,G3,G2,G1},即
{0,8,16,24}和{38,46,54,62}两个引脚组完成(在整个系统中,引脚组的名字不会重叠)。
据此,由功能和引脚组的组合就可以决定一组引脚在系统里的作用,因此在设置某组引脚的作用时,pinctrl的核心层会将功能的序号以及引脚组的序号传递给底层pinctrl驱动中相关的回调函数。
在整个系统中,驱动或板级代码调用pinmux相关的API获取引脚后,会形成一个pinctrl、使用引脚的设备、功能、引脚组的映射关系,假设在某电路板上,将让spi0设备使用pinctrl0的fspi0功能以及gspi0引脚组,让i2c0设备使用pinctrl0的fi2c0功能和gi2c0引脚组,得到如下的映射关系:
{
{"map-spi0", spi0, pinctrl0, fspi0, gspi0},
{"map-i2c0", i2c0, pinctrl0, fi2c0, gi2c0}
}
pinctrl子系统的核心会保证每个引脚的排他性,因此一个引脚如果已经被某设备用掉了,而其他的设备又申请该引脚以行使其他的功能或GPIO,则pinctrl核心层会让该次申请失败。
在特定pinctrl驱动中pinmux相关的代码主要处理如何使能/禁止某一{功能,引脚组}的组合,譬如,当spi0设备申请pinctrl0的fspi0功能和gspi0引脚组以便将gspi0引脚组配置为SPI接口时,相关的回调函数被组织进一个pinmux_ops结构体中,而该结构体的实例最终成为前文pinctrl_desc的pmxops成员,如代码清单20.20所示。
代码清单20.20 pinmux的实现
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinmux.h>
struct foo_group {
const char *name;
const unsigned int *pins;
const unsigned num_pins;
};
static const unsigned spi0_0_pins[] = { 0, 8, 16, 24 };
static const unsigned spi0_1_pins[] = { 38, 46, 54, 62 };
static const unsigned i2c0_pins[] = { 24, 25 };
static const unsigned mmc0_1_pins[] = { 56, 57 };
static const unsigned mmc0_2_pins[] = { 58, 59 };
static const unsigned mmc0_3_pins[] = { 60, 61, 62, 63 };
static const struct foo_group foo_groups[] = {
{
.name = "spi0_0_grp",
.pins = spi0_0_pins,
.num_pins = ARRAY_SIZE(spi0_0_pins),
},
{
.name = "spi0_1_grp",
.pins = spi0_1_pins,
.num_pins = ARRAY_SIZE(spi0_1_pins),
},
{
.name = "i2c0_grp",
.pins = i2c0_pins,
.num_pins = ARRAY_SIZE(i2c0_pins),
},
{
.name = "mmc0_1_grp",
.pins = mmc0_1_pins,
.num_pins = ARRAY_SIZE(mmc0_1_pins),
},
{
.name = "mmc0_2_grp",
.pins = mmc0_2_pins,
.num_pins = ARRAY_SIZE(mmc0_2_pins),
},
{
.name = "mmc0_3_grp",
.pins = mmc0_3_pins,
.num_pins = ARRAY_SIZE(mmc0_3_pins),
},
};
static int foo_get_groups_count(struct pinctrl_dev *pctldev)
{
return ARRAY_SIZE(foo_groups);
}
static const char *foo_get_group_name(struct pinctrl_dev *pctldev,
unsigned selector)
{
return foo_groups[selector].name;
}
static int foo_get_group_pins(struct pinctrl_dev *pctldev, unsigned selector,
unsigned ** const pins,
unsigned * const num_pins)
{
*pins = (unsigned *) foo_groups[selector].pins;
*num_pins = foo_groups[selector].num_pins;
return 0;
}
static struct pinctrl_ops foo_pctrl_ops = {
.get_groups_count = foo_get_groups_count,
.get_group_name = foo_get_group_name,
.get_group_pins = foo_get_group_pins,
};
struct foo_pmx_func {
const char *name;
const char * const *groups;
const unsigned num_groups;
};
static const char * const spi0_groups[] = { "spi0_0_grp", "spi0_1_grp" };
static const char * const i2c0_groups[] = { "i2c0_grp" };
static const char * const mmc0_groups[] = { "mmc0_1_grp", "mmc0_2_grp",
"mmc0_3_grp" };
static const struct foo_pmx_func foo_functions[] = {
{
.name = "spi0",
.groups = spi0_groups,
.num_groups = ARRAY_SIZE(spi0_groups),
},
{
.name = "i2c0",
.groups = i2c0_groups,
.num_groups = ARRAY_SIZE(i2c0_groups),
},
{
.name = "mmc0",
.groups = mmc0_groups,
.num_groups = ARRAY_SIZE(mmc0_groups),
},
};
static int foo_get_functions_count(struct pinctrl_dev *pctldev)
{
return ARRAY_SIZE(foo_functions);
}
static const char *foo_get_fname(struct pinctrl_dev *pctldev, unsigned selector)
{
return foo_functions[selector].name;
}
static int foo_get_groups(struct pinctrl_dev *pctldev, unsigned selector,
const char * const **groups,
unsigned * const num_groups)
{
*groups = foo_functions[selector].groups;
*num_groups = foo_functions[selector].num_groups;
return 0;
}
static int foo_set_mux(struct pinctrl_dev *pctldev, unsigned selector,
unsigned group)
{
u8 regbit = (1 << selector + group);
writeb((readb(MUX)|regbit), MUX)
return 0;
}
static struct pinmux_ops foo_pmxops = {
.get_functions_count = foo_get_functions_count,
.get_function_name = foo_get_fname,
.get_function_groups = foo_get_groups,
.set_mux = foo_set_mux,
.strict = true,
};
/* Pinmux operations are handled by some pin controller */
static struct pinctrl_desc foo_desc = {
...
.pctlops = &foo_pctrl_ops,
.pmxops = &foo_pmxops,
};
具体的pinctrl、使用引脚的设备、功能、引脚组的映射关系,可在板文件中通过定义pinctrl_map结构体的实例来展开,如:
static struct pinctrl_map __initdata mapping[] = {
PIN_MAP_MUX_GROUP("foo-i2c.o", PINCTRL_STATE_DEFAULT, "pinctrl-foo", NULL, "i2c0"),
};
又由于1个功能可由两个不同的引脚组实现,所以对于同1个功能可能形成有两个可选引脚组的pinctrl_map:
static struct pinctrl_map __initdata mapping[] = {
PIN_MAP_MUX_GROUP("foo-spi.0", "spi0-pos-A", "pinctrl-foo", "spi0_0_grp", "spi0"),
PIN_MAP_MUX_GROUP("foo-spi.0", "spi0-pos-B", "pinctrl-foo", "spi0_1_grp", "spi0"),
};
其中调用的PIN_MAP_MUX_GROUP是一个宏,用于赋值pinctrl_map的各个成员:
#define PIN_MAP_MUX_GROUP(dev, state, pinctrl, grp, func) \
{ \
.dev_name = dev, \
.name = state, \
.type = PIN_MAP_TYPE_MUX_GROUP, \
.ctrl_dev_name = pinctrl, \
.data.mux = { \
.group = grp, \
.function = func, \
}, \
}
这种映射关系最好是在设备树中通过节点的属性进行,具体的节点属性的定义方法依赖于具体的pinctrl驱动,最终在pinctrl驱动中通过pinctrl_ops结构体的.dt_node_to_map()成员函数读出属性并建立映射表。
在运行时,可以通过类似的API去查找并设置位置A的引脚组以行使SPI接口的功能:
p = devm_pinctrl_get(dev);
s = pinctrl_lookup_state(p, "spi0-pos-A ");
ret = pinctrl_select_state(p, s);
或者可以更加简单地使用:
p = devm_pinctrl_get_select(dev, "spi0-pos-A");
若在运行时切换位置A和B的引脚组以行使SPI的接口功能,代码结构类似清单20.21所示。
代码清单20.21 pinctrl_lookup_state()和pinctrl_select_stat()
#include <linux/pinctrl/consumer.h>
struct foo_state {
struct pinctrl *p;
struct pinctrl_state *s;
...
};
foo_probe()
{
/* Allocate a state holder named "foo" etc */
struct foo_state *foo = ...;
foo->p = devm_pinctrl_get(&device);
if (IS_ERR(foo->p)) {
/* FIXME: clean up "foo" here */
return PTR_ERR(foo->p);
}
foo->s = pinctrl_lookup_state(foo->p, PINCTRL_STATE_DEFAULT);
if (IS_ERR(foo->s)) {
/* FIXME: clean up "foo" here */
return PTR_ERR(s);
}
ret = pinctrl_select_state(foo->s);
if (ret < 0) {
/* FIXME: clean up "foo" here */
return ret;
}
}
对于"default"状态下的引脚配置,驱动一般不需要完成devm_pinctrl_get_select(dev,"default")的调用。譬如对于arch/arm/boot/dts/prima2-evb.dts中的如下引脚组:
peri-iobg {
uart@b0060000 {
pinctrl-names = "default";
pinctrl-0 = <&uart1_pins_a>;
};
spi@b00d0000 {
pinctrl-names = "default";
pinctrl-0 = <&spi0_pins_a>;
};
spi@b0170000 {
pinctrl-names = "default";
pinctrl-0 = <&spi1_pins_a>;
};
};
由于pinctrl-names都是"default"的,所以pinctrl核实际会自动做类似devm_pinctrl_get_select(dev,"default")的操作。
20.8 时钟驱动
在一个SoC中,晶振、PLL、驱动和门等会形成一个时钟树形结构,在Linux 2.6中,也存有clk_get_rate()、clk_set_rate()、clk_get_parent()、clk_set_parent()等通用API,但是这些API由每个SoC单独实现,而且各个SoC供应商在实现方面的差异很大,于是内核增加了一个新的通用时钟框架以解决这个碎片化问题。之所以称为通用时钟,是因为这个通用主要体现在:
1)统一的clk结构体,统一的定义于clk.h中的clk API,这些API会调用统一的clk_ops中的回调函数;这个统一的clk结构体的定义如代码清单20.22所示。
代码清单20.22 clk结构体
struct clk {undefined
const char *name;
const struct clk_ops *ops;// 关于时钟使能、禁止、计算频率等的操作集
struct clk_hw *hw;
char **parent_names;
struct clk **parents;
struct clk *parent;
struct hlist_head children;
struct hlist_node child_node;
...
};
代码清单20.23 clk_ops结构体
include/linux/clk-provider.h
struct clk_ops {undefined
int (*prepare)(struct clk_hw *hw);
void (*unprepare)(struct clk_hw *hw);
int (*is_prepared)(struct clk_hw *hw);
void (*unprepare_unused)(struct clk_hw *hw);
int (*enable)(struct clk_hw *hw);
void (*disable)(struct clk_hw *hw);
int (*is_enabled)(struct clk_hw *hw);
void (*disable_unused)(struct clk_hw *hw);
unsigned long (*recalc_rate)(struct clk_hw *hw,
unsigned long parent_rate);
long (*round_rate)(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate);
int (*determine_rate)(struct clk_hw *hw,
struct clk_rate_request *req);
int (*set_parent)(struct clk_hw *hw, u8 index);
u8 (*get_parent)(struct clk_hw *hw);
int (*set_rate)(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate);
int (*set_rate_and_parent)(struct clk_hw *hw,
unsigned long rate,
unsigned long parent_rate, u8 index);
unsigned long (*recalc_accuracy)(struct clk_hw *hw,
unsigned long parent_accuracy);
int (*get_phase)(struct clk_hw *hw);
int (*set_phase)(struct clk_hw *hw, int degrees);
void (*init)(struct clk_hw *hw);
int (*debug_init)(struct clk_hw *hw, struct dentry *dentry);
};
2)对具体的SoC如何去实现针对自己SoC的clk驱动,如何提供硬件特定的回调函数的方法也进行了统一。
struct clk_hw 是联系clk_ops中回调函数和具体硬件细节的纽带,clk_hw中只包含通用时钟结构体的指针以及具体硬件的init数据,如代码清单20.24所示。
代码清单20.24 clk_hw结构体
include/linux/clk-provider.h
struct clk_hw {undefined
struct clk_core *core;
struct clk *clk;
const struct clk_init_data *init;
};
其中的clk_init_data包含了具体时钟的名称、可能的父级时钟的名称列表parent_names、可能的父级时钟数
量num_parents等,这些名称的匹配对建立时钟间的父子关系功不可没,如代码清单20.25所示。
代码清单20.25 clk_init_data结构体
include/linux/clk-provider.h
struct clk_init_data {undefined
const char *name;
const struct clk_ops *ops;
const char **parent_names;
u8 num_parents;
unsigned long flags;
};
从clk核心层到具体芯片clk驱动的调用顺序为:
clk_enable(clk);-------> clk->ops->enable(clk->hw);
通用的clk API(如clk_enable)在调用底层clk结构体的clk_ops成员函数(如clk->ops->enable)时,会将
clk->hw传递过去。
一般在具体的驱动中会定义针对特定clk的结构体,该结构体中包含clk_hw成员以及硬件私有数据:
struct clk_foo {undefined
struct clk_hw hw;
... hardware specific data goes here ...
}
并定义to_clk_foo()宏,以便通过clk_hw获取clk_foo:
#define to_clk_foo(_hw) container_of(_hw, struct clk_foo, hw)
在针对clk_foo的clk_ops的回调函数中,可以通过clk_hw和to_clk_foo最终获得硬件私有数据,并访问硬件读写寄存器以改变时钟的状态:
struct clk_ops clk_foo_ops {undefined
.enable = &clk_foo_enable;
.disable = &clk_foo_disable;
};
int clk_foo_enable(struct clk_hw *hw)
{undefined
struct clk_foo *foo;
foo = to_clk_foo(hw);
/* 访问硬件读写寄存器以改变时钟的状态 */
…
return 0;
};
在具体的clk驱动中,需要通过clk_register()以及它的变体注册硬件上所有的clk,通过
clk_register_clkdev()注册clk的一个lookup,这两个函数的原型为:
struct clk *clk_register(struct device *dev, struct clk_hw *hw);
int clk_register_clkdev(struct clk *clk, const char *con_id, const char *dev_fmt, ...);
另外,针对不同的clk类型(如固定频率的clk、clk门、clk驱动等),clk子系统又提供了几个快捷函数以完成clk_register()的过程:
struct clk *clk_register_fixed_rate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags, unsigned long fixed_rate);
struct clk *clk_register_gate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags, void __iomem *reg, u8 bit_idx,
u8 clk_gate_flags, spinlock_t *lock);
struct clk *clk_register_divider(struct device *dev, const char *name,
const char *parent_name, unsigned long flags, void __iomem *reg, u8 shift, u8 width,
u8 clk_divider_flags, spinlock_t *lock);
以drivers/clk/sirf/clk-prima2.c为例,与该驱动对应的芯片SiRFprimaII的外围接了一个26MHz的晶振和一个
32.768kHz的RTC晶振,在26MHz晶振的后面又有3个PLL,PLL后面又接了更多的clk节点,相关驱动代码如清单20.26所示。
代码清单20.26 clk驱动案例
static unsigned long pll_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
{undefined
unsigned long fin = parent_rate;
struct clk_pll *clk = to_pllclk(hw);
…
}
static long pll_clk_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate)
{undefined
…
}
static int pll_clk_set_rate(struct clk_hw *hw, unsigned long rate,unsigned long parent_rate)
{undefined
…
}
static struct clk_ops std_pll_ops = {undefined
.recalc_rate = pll_clk_recalc_rate,
.round_rate = pll_clk_round_rate,
.set_rate = pll_clk_set_rate,
};
static const char *pll_clk_parents[] = {undefined
"osc",
};
static struct clk_init_data clk_pll1_init = {undefined
.name = "pll1",
.ops = &std_pll_ops,
.parent_names = pll_clk_parents,
.num_parents = ARRAY_SIZE(pll_clk_parents),
};
static struct clk_init_data clk_pll2_init = {undefined
.name = "pll2",
.ops = &std_pll_ops,
.parent_names = pll_clk_parents,
.num_parents = ARRAY_SIZE(pll_clk_parents),
};
static struct clk_init_data clk_pll3_init = {undefined
.name = "pll3",
.ops = &std_pll_ops,
.parent_names = pll_clk_parents,
.num_parents = ARRAY_SIZE(pll_clk_parents),
};
static struct clk_pll clk_pll1 = {undefined
.regofs = SIRFSOC_CLKC_PLL1_CFG0,
.hw = {undefined
.init = &clk_pll1_init,
},
};
static struct clk_pll clk_pll2 = {undefined
.regofs = SIRFSOC_CLKC_PLL2_CFG0,
.hw = {undefined
.init = &clk_pll2_init,
},
};
static struct clk_pll clk_pll3 = {undefined
.regofs = SIRFSOC_CLKC_PLL3_CFG0,
.hw = {undefined
.init = &clk_pll3_init,
},
};
void __init sirfsoc_of_clk_init(void)
{undefined
…
/* These are always available (RTC and 26MHz OSC)*/
clk = clk_register_fixed_rate(NULL, "rtc", NULL, CLK_IS_ROOT, 32768);
BUG_ON(!clk);
clk = clk_register_fixed_rate(NULL, "osc", NULL, CLK_IS_ROOT, 26000000);
BUG_ON(!clk);
clk = clk_register(NULL, &clk_pll1.hw);
BUG_ON(!clk);
clk = clk_register(NULL, &clk_pll2.hw);
BUG_ON(!clk);
clk = clk_register(NULL, &clk_pll3.hw);
BUG_ON(!clk);
…
}
目前内核更加倡导的方法是通过设备树来描述电路板上的时钟树,以及时钟和设备之间的绑定关系。通常需要在clk控制器的节点中定义#clock-cells属性,并且在clk驱动中通过of_clk_add_provider()注册时钟控制器为一个时钟树的提供者(Provider),并建立系统中各个时钟和索引的映射表,如:
在每个具体的设备中,对应的.dts节点上的clocks=<&clks index>属性指向其引用的clk控制器节点以及
使用的时钟的索引,如:
gps@a8010000 {undefined
compatible = "sirf,prima2-gps";
reg = <0xa8010000 0x10000>;
interrupts = <7>;
clocks = <&clks 9>;
};
要特别强调的是,在具体的设备驱动中,一定要通过通用clk API来操作所有的时钟,而不要直接通过读写clk控制器的寄存器来进行,这些API包括:
struct clk *clk_get(struct device *dev, const char *id);
struct clk *devm_clk_get(struct device *dev, const char *id);
int clk_enable(struct clk *clk);
int clk_prepare(struct clk *clk);
void clk_unprepare(struct clk *clk);
void clk_disable(struct clk *clk);
static inline int clk_prepare_enable(struct clk *clk);
static inline void clk_disable_unprepare(struct clk *clk);
unsigned long clk_get_rate(struct clk *clk);
int clk_set_rate(struct clk *clk, unsigned long rate);
struct clk *clk_get_parent(struct clk *clk);
int clk_set_parent(struct clk *clk, struct clk *parent);
备注:
名称中含有prepare、unprepare字符串的API是内核后来才加入的,过去只有clk_enable()和clk_disable()。只有clk_enable()和clk_disable()带来的问题是,有时候,某些硬件使能/禁止时钟可能会引起睡眠以使得使能/禁止不能在原子上下文进行。加上prepare后,把过去的clk_enable()分解成不可在原子上下文调用的clk_prepare()(该函数可能睡眠)和可以在原子上下文调用的clk_enable()。而clk_prepare_enable()则同时完成准备和使能的工作,当然也只能在可能睡眠的上下文调用该API。
20.9 dma engine驱动
dma engine是一套通用的DMA(直接存储器存取)驱动框架,该框架为使用DMA通道的设备驱动提供了一套统一的API,而且也定义了用具体的DMA控制器实现这一套API的方法。
对于使用DMA引擎的设备驱动而言,发起DMA传输的过程变得整洁了,如在sound子系统的sound/core/pcm_dmaengine.c中,会使用dmaengine进行周期性的DMA传输,相关的代码如清单20.27所
示。
代码清单20.27 dma engine API的使用
static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream)
{undefined
struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
struct dma_chan *chan = prtd->dma_chan;
struct dma_async_tx_descriptor *desc;
enum dma_transfer_direction direction;
unsigned long flags = DMA_CTRL_ACK;
direction = snd_pcm_substream_to_dma_direction(substream);
if (!substream->runtime->no_period_wakeup)
flags |= DMA_PREP_INTERRUPT;
prtd->pos = 0;
desc = dmaengine_prep_dma_cyclic(chan,
substream->runtime->dma_addr,
snd_pcm_lib_buffer_bytes(substream),
snd_pcm_lib_period_bytes(substream), direction, flags);//初始化一个具体的周期性的DMA传输描述符
if (!desc)
return -ENOMEM;
desc->callback = dmaengine_pcm_dma_complete;
desc->callback_param = substream;
prtd->cookie = dmaengine_submit(desc);
return 0;
}
int snd_dmaengine_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{undefined
struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
int ret;
switch (cmd) {undefined
case SNDRV_PCM_TRIGGER_START:
ret = dmaengine_pcm_prepare_and_submit(substream);
if (ret)
return ret;
dma_async_issue_pending(prtd->dma_chan);
break;
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
dmaengine_resume(prtd->dma_chan);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
if (runtime->info & SNDRV_PCM_INFO_PAUSE)
dmaengine_pause(prtd->dma_chan);
else
dmaengine_terminate_all(prtd->dma_chan);
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
dmaengine_pause(prtd->dma_chan);
break;
case SNDRV_PCM_TRIGGER_STOP:
dmaengine_terminate_all(prtd->dma_chan);
break;
default:
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_trigger);
这个过程可分为4步:
1)通过dmaengine_prep_dma_xxx()初始化一个具体的DMA传输描述符(本例中为结构体
dma_async_tx_descriptor的实例desc,本例是一个周期性DMA,调用的是
dmaengine_prep_dma_cyclic())。
2)通过dmaengine_submit()将该描述符插入dmaengine驱动的传输队列。
3)在需要传输时通过类似dma_async_issue_pending()的调用启动对应DMA通道上的传输。
4)DMA的完成,或者周期性DMA完成了一个周期,都会引发DMA传输描述符的完成回调函数被调用(本例中对应的回调函数为dmaengine_pcm_dma_complete)。
也就是不管具体硬件的DMA控制器是如何实现的,在软件意义上都抽象为了设置DMA描述符、将DMA描述符插入传输队列以及启动DMA传输的过程。
除前文提到的用dmaengine_prep_dma_cyclic()定义周期性DMA传输外,还有一组类似的API可以用来定义各种类型的DMA描述符,特定硬件的DMA驱动的主要工作就是实现封装在内核dma_device结构体中的这些成员函数。
include/linux/dmaengine.h
struct dma_device {undefined
unsigned int chancnt;
unsigned int privatecnt;
struct list_head channels;
struct list_head global_node;
struct dma_filter filter;
dma_cap_mask_t cap_mask;
unsigned short max_xor;
unsigned short max_pq;
enum dmaengine_alignment copy_align;
enum dmaengine_alignment xor_align;
enum dmaengine_alignment pq_align;
enum dmaengine_alignment fill_align;
#define DMA_HAS_PQ_CONTINUE (1 << 15)
int dev_id;
struct device *dev;
u32 src_addr_widths;
u32 dst_addr_widths;
u32 directions;
u32 max_burst;
bool descriptor_reuse;
enum dma_residue_granularity residue_granularity;
int (*device_alloc_chan_resources)(struct dma_chan *chan);
void (*device_free_chan_resources)(struct dma_chan *chan);
struct dma_async_tx_descriptor *(*device_prep_dma_memcpy)(
struct dma_chan *chan, dma_addr_t dst, dma_addr_t src,
size_t len, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_xor)(
struct dma_chan *chan, dma_addr_t dst, dma_addr_t *src,
unsigned int src_cnt, size_t len, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_xor_val)(
struct dma_chan *chan, dma_addr_t *src, unsigned int src_cnt,
size_t len, enum sum_check_flags *result, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_pq)(
struct dma_chan *chan, dma_addr_t *dst, dma_addr_t *src,
unsigned int src_cnt, const unsigned char *scf,
size_t len, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_pq_val)(
struct dma_chan *chan, dma_addr_t *pq, dma_addr_t *src,
unsigned int src_cnt, const unsigned char *scf, size_t len,
enum sum_check_flags *pqres, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_memset)(
struct dma_chan *chan, dma_addr_t dest, int value, size_t len,
unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_memset_sg)(
struct dma_chan *chan, struct scatterlist *sg,
unsigned int nents, int value, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_interrupt)(
struct dma_chan *chan, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_slave_sg)(
struct dma_chan *chan, struct scatterlist *sgl,
unsigned int sg_len, enum dma_transfer_direction direction,
unsigned long flags, void *context);
struct dma_async_tx_descriptor *(*device_prep_dma_cyclic)(
struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
size_t period_len, enum dma_transfer_direction direction,
unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_interleaved_dma)(
struct dma_chan *chan, struct dma_interleaved_template *xt,
unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_dma_imm_data)(
struct dma_chan *chan, dma_addr_t dst, u64 data,
unsigned long flags);
int (*device_config)(struct dma_chan *chan,
struct dma_slave_config *config);
int (*device_pause)(struct dma_chan *chan);
int (*device_resume)(struct dma_chan *chan);
int (*device_terminate_all)(struct dma_chan *chan);
void (*device_synchronize)(struct dma_chan *chan);
enum dma_status (*device_tx_status)(struct dma_chan *chan,
dma_cookie_t cookie,
struct dma_tx_state *txstate);
void (*device_issue_pending)(struct dma_chan *chan);
};
在底层的dmaengine驱动实例中,一般会组织好这个dma_device结构体,并通过
dma_async_device_register()完成注册。在其各个成员函数中,一般会通过链表来管理DMA描述符的运行、free等队列。
dma_device结构体的成员函数device_issue_pending()用于实现DMA传输开启的功能,每当DMA传输完成后,在驱动中注册的中断服务程序的上半部或者下半部会调用DMA描述符dma_async_tx_descriptor中设置
的回调函数,该回调函数来源于使用DMA通道的设备驱动。
典型的dmaengine驱动可见于drivers/dma/目录下的sirf-dma.c、omap-dma.c、pl330.c、ste_dma40.c等。