系列文章目录
-
Exynos4412 移植Linux-6.1(一)下载、配置、编译Linux-6.1
-
Exynos4412 移植Linux-6.1(二)SD卡驱动——解决无法挂载SD卡的根文件系统
-
Exynos4412 移植Linux-6.1(三)SD卡驱动——解决mmc0: Timeout waiting for hardware interrupt.
-
Exynos4412 移植Linux-6.1(四)NandFlash卡驱动
-
Exynos4412 移植Linux-6.1(五)DM9000网卡驱动
-
Exynos4412 移植Linux-6.1(六)【已解决】SROMC寄存器的数值不正确的问题
-
Exynos4412 移植Linux-6.1 (七)挂载Ramdisk文件系统,【已解决】Couldn’t find valid RAM disk image starting at 0
-
Exynos4412 移植Linux-6.1 (八)LCD驱动,解决error: implicit declaration of function ‘dma_free_writecombine’的问题
-
Exynos4412 移植Linux-6.1(九)移植tiny4412_backlight驱动的过程及问题解决
Exynos4412 移植Linux-6.1(九)移植tiny4412_backlight驱动的过程及问题解决
- 系列文章目录
- 1、背光的工作原理及电路
- (1) 什么是背光:
- (2)背光电路
- (3)一线触控
- (4)背光PWM控制信号接口
- 2、移植背光驱动程序
- (1)修改tiny4412_backlight设备树
- (2)修改backlight驱动
- 问题1:platform_get_resource无法获取irq资源
- 问题2:irq指针为NULL
- 问题3:devm_ioremap_resource不能重复映射res虚拟地址
- 问题4:rmmod backlight_drv出错
- 3、测试
LCD的驱动就移植完成了,但是LCD屏幕并没有亮。这有可能是因为没有移植backlight驱动。
1、背光的工作原理及电路
(1) 什么是背光:
LCD本身是不发光的,因此要想让其显示所要数据和图像,需要一个外部面光源系统来帮助其显示,即背光源(Backlight)。LCD的白光背光源一般由6~8个直下式或侧入式侧发光白色LED灯组成。背光源的工作原理,就是将灯条等点光源,利用导光板、反射片、扩散膜、增光膜(棱镜片)等组件转换成面光源,为LCD产品提供显示所需的外部光源。(https://blog.csdn.net/m0_66322708/article/details/124241892/)
(2)背光电路
EUP 2584是专为驱动白色LED而设计的一种恒流升压变换器。EN引脚用来控制LED的亮灭。FB引脚接收不同的占空比来驱动LCD的背光的亮度。LX引脚为LED背光源提供电源。
(3)一线触控
在 Cortex-A9智能终端中,LCD 背光开关是通过Exynos4412的GPX3_2作为EINT10,连接EPU2584的EN端口。GPX3_2输出为高电平“1”时,将打开背光;当输出为低电平“0”时,将关闭背光。
(4)背光PWM控制信号接口
背光PWM控制是通过Cortex-A9智能终端 的GPD0_1 作为TOUT1输出pwm信号给EPU2584的FB端口。在智能终端的背光电路中,通过在10%~90%之间调整PWM占空比,来调整背光电路的输出电流在20.5mA到5.5mA之间变化,从而实现背光灯源亮度的调整。
2、移植背光驱动程序
与tiny4412的电路是类似的,所以移植tiny4412的背光驱动。比较完整的tiny4412_backlightd 驱动代码如下:https://github.com/hceng/learn/blob/master/tiny4412/01_backlight_drv/backlight_drv.c
但是,在移植该驱动到Linux-6.1的过程中,出现了很多问题。现将问题和解决过程记录下来,以供大家参考。
(1)修改tiny4412_backlight设备树
tiny4412的设备树如下:
/ {
[...]
backlight_demo@139D0000{
compatible = "tiny4412,backlight";
reg = <0x139D0000 0x14>;
tiny4412,backlight = <&gpx1 2 GPIO_ACTIVE_HIGH>;
pinctrl-names = "backlight_out","backlight_in";
pinctrl-0 = <&backlight_out>;
pinctrl-1 = <&backlight_in>;
interrupts = <0 40 0>;
clocks = <&clock CLK_PWM>;
clock-names = "timers";
};
};
[...]
&pinctrl_1 {
backlight_out: backlight_out{
samsung,pins = "gpx1-2";
samsung,pin-function = <1>;
samsung,pin-pud = <0>;
samsung,pin-drv = <0>;
};
backlight_in: backlight_in{
samsung,pins = "gpx1-2";
samsung,pin-function = <0>;
samsung,pin-pud = <0>;
samsung,pin-drv = <0>;
};
};
根据自己开发板的电路引脚,修改如下:
backlight {
compatible = "tiny4412,backlight";
reg = <0x139D0000 0x1000>;
pinctrl-names = "backlight_in", "backlight_out";
pinctrl-0 = <&backlight_in>;
pinctrl-1 = <&backlight_out>;
gpios = <&gpx3 2 GPIO_ACTIVE_HIGH>;
clock-names = "timers";
clocks = <&clock CLK_PWM>;
interrupts = <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>;
pwms = <&pwm 0 1000000000 0>;
};
[...]
&pinctrl_1 {
backlight_out: backlight_out {
samsung,pins = "gpx3-2";
samsung,pin-function = <EXYNOS_PIN_FUNC_OUTPUT>;
samsung,pin-pud = <EXYNOS_PIN_PULL_NONE>;
samsung,pin-drv = <EXYNOS4_PIN_DRV_LV1>;
};
backlight_in: backlight_in {
samsung,pins = "gpx3-2";
samsung,pin-function = <EXYNOS_PIN_FUNC_INPUT>;
samsung,pin-pud = <EXYNOS_PIN_PULL_NONE>;
samsung,pin-drv = <EXYNOS4_PIN_DRV_LV1>;
};
};
(2)修改backlight驱动
问题1:platform_get_resource无法获取irq资源
tiny4412_backlight中会用到pwm的中断。我没有看懂onewire的原理。为了移植驱动,需要获取irq资源。
原驱动中是通过如下代码获取irq资源的。但是在Linux-6.1里,irq始终都为NULL。
irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (irq == NULL)
{
printk("platform_get_resource irq error\n");
return -EINVAL;
}
然后,我在probe函数中添加for循环,打印resource,发现num_resources是1,也就是只有一个reg资源,而没有irq资源。
printk("num_resources: = %d", pdev->num_resources);
int i;
for (i = 0; i < pdev->num_resources; i++) {
struct resource *r = &pdev->resource[i];
printk("resource_type: =%ld\n",resource_type(r));
接着,我试着在probe函数中用以下代码查看Exynos4.dtsi中把pwm转换成platform_device之后的resource,仍然只有一个reg资源,而没有irq资源。
struct device_node *pwd_node = NULL;
struct platform_device *pdev_pwd;
pwd_node = of_find_compatible_node(NULL,NULL,"samsung,exynos4210-pwm");
if(pwd_node == NULL)
{
printk("of_find_node_by_name is error\n");
return -EINVAL;
}
pdev_pwd = of_find_device_by_node(pwd_node);
if(pdev_pwd == NULL)
{
printk("of_find_node_by_name is error\n");
return -EINVAL;
}
printk("num_resources: = %d", pdev_pwd->num_resources);
for (i = 0; i < pdev_pwd->num_resources; i++) {
struct resource *r = &pdev_pwd->resource[i];
printk("resource_type: =%ld\n",resource_type(r));
}
最后,查看of_address_to_resource
、of_irq_to_resource
等内核代码,想找出没有转换irq资源的原因。但是,逻辑太复杂,没看懂,似乎和父节点还有关系。总之,新版本的内核,对设备树的解析似乎不同以前。
没办法,只能自己通过devic_node来获取irq。在platform_device中有一个成员struct device dev,这个dev中又有一个指针成员struct device_node * of_node。linux的做法就是将这个of_node指针直接指向由设备树转换而来的device_node结构;留给驱动开发者自行处理。
例如,有这么一个struct platform_device* of_test.我们可以直接通过of_test->dev.of_node来访问设备树中的信息。
我的做法是在backlight_probe函数中,添加代码实现以下2个功能。
- 利用
of_irq_get
或者irq_of_parse_and_map
获取irq号。
这两个函数获取的irq号是一样的。这个irq号不是硬件数据手册中的硬件irq号。 - 填充irq指针的resource结构体
问题2:irq指针为NULL
这里又出现了另一个问题,就是platform_get_resource无法获取irq资源,导致irq指针始终是NULL。怎样给irq指针赋值呢?我的方法是先获得reg的resource结构体地址res ,然后把res 地址+sizeof(*res),作为irq的地址。
不知道您是否还有更好的方法,请在评论区留言。我的代码如下:
static int backlight_probe(struct platform_device *pdev)
{
int ret, irqno;
dev_t devid;
dev = &pdev->dev;
struct device_node *dev_node;
printk("enter %s\n", __func__);
dev_node = dev->of_node;
irqno = of_irq_get(dev_node, 0);
printk("of_irq_get No: %d\n", irqno);
irqno = irq_of_parse_and_map(dev_node,0);
printk("irq_of_parse_and_map No: %d\n", irqno);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
irq = res + sizeof(*res);
memset(irq, 0, sizeof(*irq));
irq->start = irq->end = irqno;
// irq->flags = IORESOURCE_IRQ;
// irq->name = of_node_full_name(dev_node);
问题3:devm_ioremap_resource不能重复映射res虚拟地址
原驱动中是利用timer = devm_ioremap_resource(&pdev->dev, res);
来映射pwm寄存器的虚拟地址的。但是,由于在arch/arm/boot/dts/exynos4.dtsi中,已经定义了pwm节点。如果它的status = "okay"
,在解析设备树的时候,就已经把reg = <0x139D0000 0x1000>;映射了一次。那么,devm_ioremap_resource
再次映射该0x139D0000地址时就会报错。
[T90] timer_phyaddr: 139d0000
[T90] tiny4412_backlight 139d0000.backlight: can't request region for resource [mem 0x139d0000-0x139d0fff]
[T90] timer_virtaddr: fffffff0
[T90] 8<--- cut here ---
[T90] Unable to handle kernel paging request at virtual address fffffff0
[T90] [fffffff0] *pgd=6fffd861, *pte=00000000, *ppte=00000000
[T90] Internal error: Oops: 837 [#1] PREEMPT SMP ARM
[T90] Modules linked in: backlight_drv(O+)
同一个物理地址,可以被映射为多个虚拟地址,所以我的解决方法是直接用ioremap
。不知道您是否还有更好的方法,请在评论区留言。
timer = ioremap(res->start, resource_size(res));
if (timer == NULL)
{
printk("devm_ioremap_resource error\n");
return -EINVAL;
}
printk("timer_virtaddr: %x\n", timer);
问题4:rmmod backlight_drv出错
解决了前3个问题之后,就可以交叉编译出backlight_drv.ko。可以在智能终端上正常insmod backlight_drv.ko,但是rmmod backlight_drv会报错。这个问题我还没有解决,如果有小伙伴知道原因之后,在评论区多多指导。
backlight_drv驱动代码资源链接
3、测试
驱动代码资源中有测试程序,可以实现0~127档的亮光调节。
insmod backlight_drv.ko
之后,在智能终端中执行如下命令,可以看到backlight的设备号是243。然后,执行mknod /dev/backlight c 243 0
创建设备文件。加载了lcd驱动之后,就能显示了。
要注意:
- 根据自己移植的Linux内核版本来修改Makefile文件。
- 还需要交叉编译test_backlight.c。
[root@farsight ]# cd /sys/class/onewire_backlight/tiny4412_backlight
[root@farsight tiny4412_backlight]# ls
dev power subsystem uevent
[root@farsight tiny4412_backlight]# cat dev
243:0
[root@farsight 04_backlight_drv]# mknod /dev/backlight c 243 0
[root@farsight 04_backlight_drv]# ./test_backlight
[...]
[ 521.416003][ T99] kernel: reg = 126
[ 521.416047][ T99] backlight_write
backlight: 126
[ 521.472414][ T99] kernel: reg = 127
[ 521.472482][ T99] backlight_write
backlight: 127
[ 521.525357][ T99] backlight_exit
done!
下一步,实现启动系统自动加载。