一,前言
开始玩所有板子一般都是先点灯,比如我可以在汇编中点灯,可以在board_init中用writel写寄存器点灯,当我要进一步熟悉下设备树驱动模型,不管学习linux还是学习uboot这块我理解类似,所以我要通过添加设备树及配置开关来实现默认打开led0和led1的功能。
二,实现的过程
1,先有了dts信息,我从其他TI的dts中copy来的,然后按原理图修改了pin脚。
leds {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&led_gpio_pins>;
led0 {
label = "led0";
gpios = <&gpio1 20 GPIO_ACTIVE_HIGH>;
default-state = "keep";
};
led1 {
label = "led1";
gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>;
default-state = "keep";
};
};
2,然后增加了配置 CONFIG_LED_GPIO=y CONFIG_LED=y CONFIG_DM_GPIO=y。
3,搜索compatible = "gpio-leds"的关键字,找到driver为gpio_led_wrap。
static const struct udevice_id led_gpio_ids[] = {
{ .compatible = "gpio-leds" },
{ }
};
U_BOOT_DRIVER(led_gpio_wrap) = {
.name = "gpio_led_wrap",
.id = UCLASS_NOP,
.of_match = led_gpio_ids,
.bind = led_gpio_bind,
};
4,我没找错吧!感觉钩子函数少啊! 我之前在device_bind_common函数添加了打印信息Device 'leds' Driver 'gpio_led_wrap',drv->bind addr is 0x9ff8a235。0x9ff8a235-0x1f764000=80826235,一般函数有字节对齐填充,所以搜索到如下为drv->bind,说明我没找错。
.text.led_gpio_bind
0x0000000080826234 0x44 drivers/led/led_gpio.o
于是再通过dm来看信息,原来子项的device就是led设备对象也有驱动,叫gpio_led。
nop 0 [ ] gpio_led_wrap |-- leds
led 0 [ ] gpio_led | |-- led0
led 1 [ ] gpio_led | `-- led1
5,现在问题来了led0和led1没有compatible属性,是怎么配对的呢?
U_BOOT_DRIVER(led_gpio) = {
.name = "gpio_led",
.id = UCLASS_LED,
.ops = &gpio_led_ops,
.priv_auto = sizeof(struct led_gpio_priv),
.probe = led_gpio_probe,
.remove = led_gpio_remove,
};
那么我只能搜索"gpio_led"关键字了,仅找到led_gpio_bind函数调用了,也就是gpio_led_wrap驱动进行drv->bind的时候会调用。
static int led_gpio_bind(struct udevice *parent)
{
...
dev_for_each_subnode(node, parent) {
ret = device_bind_driver_to_node(parent, "gpio_led",
ofnode_get_name(node),
node, &dev);
if (ret)
return ret;
}
...
}
来看下关键函数device_bind_driver_to_node的含义,uboot的注释也的很好,只要看api注释即可
/**
* device_bind_driver_to_node() - bind a device to a driver for a node
*
* This binds a new device to a driver for a given device tree node. This
* should only be needed if the node lacks a compatible strings.
*
* @parent: Parent device
* @drv_name: Name of driver to attach to this parent
* @dev_name: Name of the new device thus created
* @node: Device tree node
* @devp: If non-NULL, returns the newly bound device
* Return: 0 if OK, -ve on error
*/
int device_bind_driver_to_node(struct udevice *parent, const char *drv_name,
const char *dev_name, ofnode node,
struct udevice **devp);
之前我提出的问题是”led0和led1没有compatible属性,是怎么配对的呢?“原来就是用此函数,适用于在缺少compatible字符的时候,无条件绑定子node的名称作为设备。 这种不匹配而直接绑定其子node的方法感觉不太好,因为搜索起来不方便了。 结果上电后,没有调用led_gpio_probe函数。led灯也没亮。
6,于是看led驱动drivers/led/led-uclass.c中default-state应该设置为on或off,而我设置为keep了。
static int led_post_bind(struct udevice *dev)
{
struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev);
const char *default_state;
uc_plat->label = dev_read_string(dev, "label");
if (!uc_plat->label)
uc_plat->label = ofnode_get_name(dev_ofnode(dev));
uc_plat->default_state = LEDST_COUNT;
default_state = dev_read_string(dev, "default-state");
if (!default_state)
return 0;
if (!strncmp(default_state, "on", 2))
uc_plat->default_state = LEDST_ON;
else if (!strncmp(default_state, "off", 3))
uc_plat->default_state = LEDST_OFF;
else
return 0;
7,修改dts的keep为on后,可以进入probe了,但是led没有点亮,另外此时与预期不符。
Device 'leds' Driver 'gpio_led_wrap',drv->bind addr is 0x9ff8a24d
led_post_bind
led_post_bind
Device 'extlinux' Driver 'bootmeth_extlinux',drv->bind addr is 0x9ff6a925
Device 'efi' Driver 'bootmeth_efi',drv->bind addr is 0x9ff6abf1
Device 'pxe' Driver 'bootmeth_pxe',drv->bind addr is 0x9ff6abbd
Device 'vbe_simple' Driver 'vbe_simple',drv->bind addr is 0x9ff6cef1
Device 'led0' Driver 'gpio_led',drv->probe addr is 0x9ff8a22d
Device 'target-module@4c000' Driver 'ti_sysc',drv->probe addr is 0x9ff97ad5
Device 'target-module@0' Driver 'ti_sysc',drv->probe addr is 0x9ff97ad5
Device 'gpio@0' Driver 'gpio_omap',drv->probe addr is 0x9ff8924d
led_post_probe
Device 'led1' Driver 'gpio_led',drv->probe addr is 0x9ff8a22d
led_post_probe
go4
Device 'target-module@9000' Driver 'ti_sysc',drv->probe addr is 0x9ff97ad5
Device 'serial@0' Driver 'omap_serial',drv->probe addr is 0x9ff96bc1
go6
Core: 154 devices, 16 uclasses, devicetree: separate
Device 'timer@0' Driver 'omap_timer',drv->probe addr is 0x9ff97a3d
NAND: 0 MiB
MMC: Device 'mmc@48060000' Driver 'omap_hsmmc',drv->probe addr is 0x9ff8c4c1
Device 'target-module@7000' Driver 'ti_sysc',drv->probe addr is 0x9ff97ad5
Device 'gpio@0' Driver 'gpio_omap',drv->probe addr is 0x9ff8924d
8,我用逻辑分析仪测试下pin脚状态,发现是高高低低的波形,根本不对。难道有其它对象也在控制这2个脚,又或者我控制的gpio脚错误了?于是查看原理图,发现我控制的pin脚确实不对,错位了,然后这个pin有下拉我就不应该再做上拉了,控制模式也改了下。2个led灯就正常了。设置如下
leds {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&led_gpio_pins>;
led0 {
label = "led0";
gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>;
default-state = "on";
};
led1 {
label = "led1";
gpios = <&gpio1 22 GPIO_ACTIVE_HIGH>;
default-state = "on";
};
};
led_gpio_pins: led_gpio_pins {
pinctrl-single,pins = <
AM33XX_PADCONF(AM335X_PIN_GPMC_A5, PIN_OUTPUT, MUX_MODE7) /* gpio1_21 */
AM33XX_PADCONF(AM335X_PIN_GPMC_A6, PIN_OUTPUT, MUX_MODE7) /* gpio1_22 */
>;
9,接着玩了下cmd下的gpio命令
AP-Boot=> gpio status
Bank gpio@44e07000_:
gpio@44e07000_6: input: 0 [x] [email protected]
Bank gpio@4804c000_:
gpio@4804c000_21: output: 0 [x] led0.gpios
gpio@4804c000_22: output: 1 [x] led1.gpios
AP-Boot=> gpio toggle gpio@4804c000_21
gpio: pin gpio@4804c000_21 (gpio 53) value is 1
AP-Boot=> gpio clear gpio@4804c000_21
gpio: pin gpio@4804c000_21 (gpio 53) value is 0
AP-Boot=> gpio set gpio@4804c000_21
gpio: pin gpio@4804c000_21 (gpio 53) value is 1
AP-Boot=> gpio clear gpio 53
GPIO: 'gpio' not found
Command 'gpio' failed: Error -22
AP-Boot=> gpio clear 53
gpio: pin 53 (gpio 53) value is 0
AP-Boot=> gpio status 53
10,gpio的pin是多少呢?这个53怎么来的,猜测是53-32=21应该就是这样来的。那么这是因为pin的映射和实际的gpio映射一样。
gpio1: gpio@0 {
compatible = "ti,omap4-gpio";
gpio-ranges = <&am33xx_pinmux 0 0 8>,
<&am33xx_pinmux 8 90 4>,
<&am33xx_pinmux 12 12 16>,
<&am33xx_pinmux 28 30 4>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
reg = <0x0 0x1000>;
interrupts = <98>;
};
但是问题来了28为什么从30开始映射,不明白原理?我猜测12,12,16改成12 13 15我理解led0的pin会加1。但是依然显示的是53。先分析下53哪里来的,是通过gpio命令的响应得来的,通过代码分析,其实是调用了gpio_lookup_name,通过如下描述数据得来的。
if (devp)
*devp = desc.dev;
if (offsetp)
*offsetp = desc.offset;
if (gpiop) {
struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(desc.dev);
*gpiop = uc_priv->gpio_base + desc.offset;
}
搜索gpio_base=,很快到赋值的地方,从0开始,gpio_count光猜测就是am33xx_pinmux中的32。原来不是从设备树拿的信息。所以我修改设备树无影响。
base = 0;
uclass_foreach_dev(dev, uc) {
if (device_active(dev) && dev != removed_dev) {
uc_priv = dev_get_uclass_priv(dev);
uc_priv->gpio_base = base;
base += uc_priv->gpio_count;
}
}
找下gpio_count的赋值,看到是一个宏定义值为32,如下还有意外收获就是打印的gpio的name也是在probe赋值的。
static int omap_gpio_probe(struct udevice *dev)
{
....
sprintf(name, "gpio@%4x_", (unsigned int)plat->base);
str = strdup(name);
if (!str)
return -ENOMEM;
uc_priv->bank_name = str;
uc_priv->gpio_count = GPIO_PER_BANK;
bank->base = (void *)plat->base;
return 0;
}
11,搜索设备树中gpio-pins中的值赋给谁,在driver中搜索到pinctrl_gpio_get_pinctrl_and_offset,终于通过代码分析了解了这些值的含义。和网上说的一样,第一个为实际gpio的offset,后面为映射的pintrl的offset值。关键是为什么TI的sdk映射的值那么怪,他们是怎么设计的我并不清楚。
static int pinctrl_gpio_get_pinctrl_and_offset(struct udevice *dev, unsigned offset,
struct udevice **pctldev, unsigned int *pin_selector)
{
struct ofnode_phandle_args args;
unsigned gpio_offset, pfc_base, pfc_pins;
int ret = 0;
int i = 0;
while (ret == 0) { // 一次扫描每一行
// 将gpio-ranges的数组的一行进行拆分。
ret = dev_read_phandle_with_args(dev, "gpio-ranges", NULL, 3,i++, &args);
if (ret) {
dev_dbg(dev, "%s: dev_read_phandle_with_args: err=%d\n",
__func__, ret);
return ret;
}
// 通过node名字来找设备树中的值
ret = uclass_get_device_by_ofnode(UCLASS_PINCTRL,args.node, pctldev);
if (ret) {
dev_dbg(dev,
"%s: uclass_get_device_by_of_offset failed: err=%d\n",
__func__, ret);
return ret;
}
// 一共有4个值,第一个是node,后面3个就是如下3个。
gpio_offset = args.args[0];
pfc_base = args.args[1];
pfc_pins = args.args[2];
if (offset >= gpio_offset && offset <= gpio_offset + pfc_pins)
break;
}
/*假如我传入值为21,那么就是21 - 12 = 9, 9 + 12 = 21。也就是说明第一个参数就是gpio的pin,后面的参数是映射的pintrol的pin。*/
offset -= gpio_offset;
offset += pfc_base;
*pin_selector = offset;
return 0;
}
此函数中的offset是通过如下调用的,也就是gpio的offset,比如我的led0就是gpio1的offset值21。
/**
* pinctrl_gpio_request() - request a single pin to be used as GPIO
*
* @dev: GPIO peripheral device
* @offset: the GPIO pin offset from the GPIO controller
* @label: the GPIO pin label
* @return: 0 on success, or negative error code on failure
*/
int pinctrl_gpio_request(struct udevice *dev, unsigned offset, const char *label)
{
const struct pinctrl_ops *ops;
struct udevice *pctldev;
unsigned int pin_selector;
int ret;
ret = pinctrl_gpio_get_pinctrl_and_offset(dev, offset,&pctldev, &pin_selector);
......
三,小结
没想到配置一个led的dm驱动都有坎坷,这也算是我故意将简单的事情变复杂,本来几行汇编就解决的,我偏要用设备树驱动。但是这样才能学到东西,算是刻意练习。过程中dm,led和gpio命令都使用简单学习过了,关于gpio和pin的设备树语法含义更加清楚了,也了解了如何添加一个驱动的步骤。
标签:DM,led,Apple,bind,dev,LED,offset,gpio,drv From: https://blog.51cto.com/AppleCai/7968987