首页 > 其他分享 >uboot为LED添加DM驱动--Apple的学习笔记

uboot为LED添加DM驱动--Apple的学习笔记

时间:2023-10-21 18:00:45浏览次数:30  
标签:DM led Apple bind dev LED offset gpio drv

一,前言

开始玩所有板子一般都是先点灯,比如我可以在汇编中点灯,可以在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

相关文章

  • DML语句
    数据操纵语言DML(DataManipulationLanguage),用户通过它可以实现对数据库的基本操作。就是我们最经常用到的UPDATE、INSERT、DELETE。主要用来对数据库的数据进行一些操作。一、表记录操作-上1.1、DML概述DML操作是指对数据库中表记录的操作,主要包括表记录的插入(insert)、更新(u......
  • ISB、DSB和DMB的含义
    参考DDI0487J_a_a-profile_architecture_reference_manual.pdfB2.3DefinitionoftheArmmemorymodelB2.3.12MemorybarriersDataMemoryBarrier(DMB)DataSynchronizationBarrier(DSB)InstructionSynchronizationBarrier(ISB)DataMemoryBarrier(D......
  • DSB和DMB后面的可选项的含义
    参考资料DDI0487J_a_a-profile_architecture_reference_manual.pdfB2.3.12MemorybarriersShareabilityandaccesslimitationsonthedatabarrieroperations......
  • EDM邮件营销的标题怎么写?这样写阅读量会高吗?
    当今,随着数字化的快速发展,越来越多的企业开始采用电子邮件营销(EDMEmailmarketing)作为一种新的营销方式。而作为EDM营销的关键一环,邮件标题的重要性不言而喻。好的标题不仅能够吸引用户打开邮件,还可以在一个瞬间充分传达营销信息。因此,本文将从多个角度解析EDM邮件营销的标题该如......
  • EDM邮件经验?大佬分享教你如何写邮件
    在现代社交互动的时代,很多企业都离不开邮件这一最基本的营销工具。那么如何编写一封行之有效的邮件呢?接下来,本文将为您分享10个EDM邮件经验。1.邮件主题要精彩EDM邮件经验?邮件主题是让读者打开邮件的第一眼,必须要让人产生革新、好奇或者紧急的感觉,让读者想立即访问您的网站或营销......
  • 数仓架构图 sdm odm
       https://www.cnblogs.com/zourui4271/p/14139002.html数据总线数据仓库作为数据管理核心,必须拥有统一标准的数据输入接口与数据输出通道,才能保证数据输入输出的稳定性。但是数据输入输出会造成数据仓库的资源损耗,尤其是IO与网络,所以建设数据总线系统可把数据输入输......
  • thinkPHP 项目只需要单个项目模块,比如去掉url中的admin
    thinkPHP项目只需要单个项目模块,比如去掉url中的admin这里以thinkPHP为例这里以thinkPHP为例1、找到入口文件的index.php,加入下面的define(‘BIND_MODULE’,‘admin’);<?php//定义应用目录define('APP_PATH',__DIR__.'/application/');//路由改写define('BIND_MODUL......
  • Makefile knowledge summarization
    WildcardThewildcardinmakefileissimilarwithmacroinC/C++,itisn'tsimilarwithwildcardinlinuxshell,soitdoesn'texpendautomatically.object1=*.c//*.cobject2=$(wildcard*.cpp)//main.cppt1.cppt2.cppAutomaticallygene......
  • CentOS 8 解决 Error: Failed to download metadata for repo 'appstream': Cannot pr
      原因CentOS-8于2021年12月31日停止了源的服务。 解决办法3.1.备份原有的yum源配置文件cd/etc/yum.repos.d/mkdirbak;cp*.repobak/执行如下命令,替换配置文件内容sed-i's/$releasever/8-stream/g'CentOS*repo刷新yum缓存即可yumclean;yummakecache......
  • uboot定制自己的板子--Apple的学习笔记
    一,前言既然下载了最新的uboot版本,那么就玩玩吧,先要定制自己的板子。二,问题分析及解决1,出错信息U-BootSPL2023.10(Oct192023-19:58:50+0800)TryingtobootfromMMC1U-Boot2023.10(Oct192023-19:58:50+0800)AppleCai'sam335BoardCPU:AM335X-GPrev2.1......