1.
在以往调驱动的经历中,改动最多的就是内核,在内核中我们可以去修改设备树,修改驱动源码,在驱动中去获取设备树中相关的硬件信息,但是我们都很少在uboot中修改或者添加代码,然而我们在工作中有时候也会碰到需要在uboot中去修改相关的驱动源码,去获取设备树的相关信息,以此满足项目需求。好了话不多少直接进入正题。
板子是基于rk3368的板子,板子上挂了一块lvds屏,硬件工程师提出要修改一下屏幕的时序,原本想来应该很简单的才对,结果按照以往的思维去内核中panel-simple.c的probe函数添加代码获取设备树中的IO口,然后延时处理,同时在panel_simple_prepare做延时处理,以此满足时序要求,如下:
static int panel_simple_prepare(struct drm_panel *panel)
{
struct panel_simple *p = to_panel_simple(panel);
int err;
if (p->prepared)
return 0;
err = regulator_enable(p->supply);
if (err < 0) {
dev_err(panel->dev, "failed to enable supply: %d\n", err);
return err;
}
printk("%s----------->\r\n",__func__);
if (p->power_gpio)
{
gpiod_direction_output(p->power_gpio, 1);
msleep(10);
//printk("%s:panel->power_gpio, delay\r\n",__func__);
}
if (p->enable_gpio)
{
gpiod_direction_output(p->enable_gpio, 1);
msleep(10);
//printk("%s:panel->enable_gpio, delay\r\n",__func__);
}
if (p->reset_gpio)
{
gpiod_direction_output(p->reset_gpio, 1);
//printk("%s:panel->reset_gpio, delay\r\n",__func__);
}
msleep(10);
if (p->backlight_gpio) {
printk(KERN_ERR"[PANEL]: set backlight power on.\n");
gpiod_direction_output(p->backlight_gpio, 1);
}
if (p->desc && p->desc->delay.prepare)
msleep(p->desc->delay.prepare);
p->prepared = true;
return 0;
}
static int panel_simple_probe(struct device *dev, const struct panel_desc *desc)
{
struct device_node *backlight, *ddc;
struct panel_simple *panel;
struct panel_desc *of_desc;
u32 val;
int err;
panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
if (!panel)
return -ENOMEM;
if (!desc)
of_desc = devm_kzalloc(dev, sizeof(*of_desc), GFP_KERNEL);
else
of_desc = devm_kmemdup(dev, desc, sizeof(*of_desc), GFP_KERNEL);
if (!of_property_read_u32(dev->of_node, "bus-format", &val))
of_desc->bus_format = val;
if (!of_property_read_u32(dev->of_node, "delay,prepare", &val))
of_desc->delay.prepare = val;
if (!of_property_read_u32(dev->of_node, "delay,enable", &val))
of_desc->delay.enable = val;
if (!of_property_read_u32(dev->of_node, "delay,disable", &val))
of_desc->delay.disable = val;
if (!of_property_read_u32(dev->of_node, "delay,unprepare", &val))
of_desc->delay.unprepare = val;
panel->enabled = false;
panel->prepared = false;
panel->desc = of_desc;
panel->dev = dev;
panel->supply = devm_regulator_get(dev, "power");
if (IS_ERR(panel->supply)) {
err = PTR_ERR(panel->supply);
dev_err(dev, "failed to request power supply: %d\n", err);
return PTR_ERR(panel->supply);
}
panel->enable_gpio = devm_gpiod_get_optional(dev, "enable", 0);
if (IS_ERR(panel->enable_gpio)) {
err = PTR_ERR(panel->enable_gpio);
dev_err(dev, "failed to request enable GPIO: %d\n", err);
return err;
} else {
gpiod_direction_output(panel->enable_gpio, 1);
printk("%s:panel->enable_gpio, 0\r\n",__func__);
}
panel->power_gpio = devm_gpiod_get_optional(dev, "power", 0);
if (IS_ERR(panel->power_gpio)) {
err = PTR_ERR(panel->power_gpio);
dev_err(dev, "failed to request reset GPIO: %d\n", err);
} else {
gpiod_direction_output(panel->power_gpio, 1);
printk("%s:panel->power_gpio, 0\r\n",__func__);
}
panel->backlight_gpio = devm_gpiod_get_optional(dev, "backlight", 0);
if (IS_ERR(panel->backlight_gpio)) {
err = PTR_ERR(panel->backlight_gpio);
dev_err(dev, "failed to request reset GPIO: %d\n", err);
} else {
gpiod_direction_output(panel->backlight_gpio, 1);
}
panel->reset_gpio = devm_gpiod_get_optional(dev, "reset", 0);
if (IS_ERR(panel->reset_gpio)) {
err = PTR_ERR(panel->reset_gpio);
dev_err(dev, "failed to request reset GPIO: %d\n", err);
}
else
{
gpiod_direction_output(panel->reset_gpio, 1);
printk("%s:panel->reset_gpio, 0\r\n",__func__);
}
.....
}
2.
但是无论怎么修改发现都无法拉低power_gpio口,而硬件要求在上电过程中要把power,stb,reset完全拉低在进行相应的延时处理来达到时序要求,所以修改内核这条路就行不通,于是我们想到直接去uboot中获取设备树的中IO口信息,然后将三个IO口拉低,再做延时处理以此满足要求。下面贴出代码:
设备树:
/{
lvds_panel: lvds-panel {
status = "okay";
compatible = "simple-panel";
backlight = <&backlight>;
bus-format = <MEDIA_BUS_FMT_RBG888_1X24>;
enable-gpios = <&gpio0 RK_PD4 GPIO_ACTIVE_HIGH>;
reset-gpios = <&gpio0 RK_PB7 GPIO_ACTIVE_HIGH>;
power-gpios = <&gpio0 RK_PC3 GPIO_ACTIVE_HIGH>;
backlight-gpios = <&gpio0 RK_PC7 GPIO_ACTIVE_HIGH>;
rockchip,data-mapping = "vesa";
rockchip,data-width = <24>;
rockchip,output = "lvds";
display-timings {
native-mode = <&timing0>;
timing0: timing0 {
clock-frequency = <60000000>;
hactive = <1024>;
vactive = <600>;
hback-porch = <90>;
hfront-porch = <160>;
vback-porch = <13>;
vfront-porch = <12>;
hsync-len = <70>;
vsync-len = <10>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <0>;
pixelclk-active = <0>;
};
};
ports {
panel_in_lvds: endpoint {
remote-endpoint = <&lvds_out_panel>;
};
};
};
};
...
&lvds {
status = "okay";
rockchip,panel = <&lvds_panel>;
pinctrl-names = "default";
pinctrl-0 = <&gpio0_c3 &gpio0_c7 &gpio0_d4 &gpio0_b7>;
ports {
lvds_out: port@1 {
reg = <1>;
#address-cells = <1>;
#size-cells = <0>;
lvds_out_panel: endpoint@0 {
reg = <0>;
remote-endpoint = <&panel_in_lvds>;
};
};
};
};
&route_lvds {
status = "okay";
};
首先cpu会先执行到:
int rockchip_display_init(void)
{
const void *blob = gd->fdt_blob;
int route, child, phandle, connect, crtc_node, conn_node;
const struct rockchip_connector *conn;
const struct rockchip_crtc *crtc;
struct display_state *s;
const char *name;
printf("%s--------------------------------%d\n",__func__,__LINE__);
route = fdt_path_offset(blob, "/display-subsystem/route");
if (route < 0) {
printf("Can't find display display route node\n");
return -ENODEV;
}
printf("%s--------------------->%d\n",__func__,__LINE__);
if (!fdt_device_is_available(blob, route))
return -ENODEV;
init_display_buffer();
fdt_for_each_subnode(blob, child, route) {
if (!fdt_device_is_available(blob, child))
continue;
phandle = fdt_getprop_u32_default_node(blob, child, 0,
"connect", -1);
if (phandle < 0) {
printf("Warn: %s: can't find connect node's handle\n",
fdt_get_name(blob, child, NULL));
continue;
}
connect = fdt_node_offset_by_phandle(blob, phandle);
if (connect < 0) {
printf("Warn: %s: can't find connect node\n",
fdt_get_name(blob, child, NULL));
continue;
}
crtc_node = find_crtc_node(blob, connect);
if (!fdt_device_is_available(blob, crtc_node)) {
printf("Warn: %s: crtc node is not available\n",
fdt_get_name(blob, child, NULL));
continue;
}
crtc = rockchip_get_crtc(blob, crtc_node);
if (!crtc) {
printf("Warn: %s: can't find crtc driver\n",
fdt_get_name(blob, child, NULL));
continue;
}
conn_node = find_connector_node(blob, connect);
if (!fdt_device_is_available(blob, conn_node)) {
printf("Warn: %s: connector node is not available\n",
fdt_get_name(blob, child, NULL));
continue;
}
conn = rockchip_get_connector(blob, conn_node);
if (!conn) {
printf("Warn: %s: can't find connector driver\n",
fdt_get_name(blob, child, NULL));
continue;
}
s = malloc(sizeof(*s));
if (!s)
goto err_free;
memset(s, 0, sizeof(*s));
INIT_LIST_HEAD(&s->head);
fdt_get_string(blob, child, "logo,uboot", &s->ulogo_name);
fdt_get_string(blob, child, "logo,kernel", &s->klogo_name);
fdt_get_string(blob, child, "logo,mode", &name);
if (!strcmp(name, "fullscreen"))
s->logo_mode = ROCKCHIP_DISPLAY_FULLSCREEN;
else
s->logo_mode = ROCKCHIP_DISPLAY_CENTER;
fdt_get_string(blob, child, "charge_logo,mode", &name);
if (!strcmp(name, "fullscreen"))
s->charge_logo_mode = ROCKCHIP_DISPLAY_FULLSCREEN;
else
s->charge_logo_mode = ROCKCHIP_DISPLAY_CENTER;
s->blob = blob;
s->conn_state.node = conn_node;
s->conn_state.connector = conn;
s->crtc_state.node = crtc_node;
s->crtc_state.crtc = crtc;
s->crtc_state.crtc_id = get_crtc_id(blob, connect);
s->node = child;
connector_phy_init(s);
connector_panel_init(s);
list_add_tail(&s->head, &rockchip_display_list);
}
return 0;
err_free:
list_for_each_entry(s, &rockchip_display_list, head) {
list_del(&s->head);
free(s);
}
return -ENODEV;
}
而在这个函数中会调用fdt_path_offset它会去获得lvds主节点关于这个函数有如下说明:
fdt_path_offset
int fdt_path_offset(const void *fdt, const char *path)
eg:node = fdt_path_offset(gd->fdt_blob, “/aliases”);
功能:获得dtb下某个节点的路径path的偏移。这个偏移就代表了这个节点
fdt_device_is_available判断节点是否可用
fdt_for_each_subnode:这是一个循环去获取这个节点下的各个子节点属性原型:
/**
* fdt_for_each_subnode - iterate over all subnodes of a parent
*
* This is actually a wrapper around a for loop and would be used like so:
*
* fdt_for_each_subnode(fdt, node, parent) {
* ...
* use node
* ...
* }
*
* Note that this is implemented as a macro and node is used as iterator in
* the loop. It should therefore be a locally allocated variable. The parent
* variable on the other hand is never modified, so it can be constant or
* even a literal.
*
* @fdt: FDT blob (const void *)
* @node: child node (int)
* @parent: parent node (int)
*/
#define fdt_for_each_subnode(fdt, node, parent) \
for (node = fdt_first_subnode(fdt, parent); \
node >= 0; \
node = fdt_next_subnode(fdt, node))
-
connector_phy_init(s);
这个函数是去初始化lvds外设 -
list_add_tail(&s->head, &rockchip_display_list);
添加设备链表节点 -
connector_panel_init(s);
这个函数当中就有初始化屏幕的调用,而我们的时序就是要在这个函数中去作处理。
3.
connector_panel_init(s)的源码:
static int connector_panel_init(struct display_state *state)
{
struct connector_state *conn_state = &state->conn_state;
struct panel_state *panel_state = &state->panel_state;
const void *blob = state->blob;
int conn_node = conn_state->node;
const struct rockchip_panel *panel;
int panel_node;
int ret;
printf("%s---------------------%d\r\n",__func__,__LINE__);
panel_node = get_panel_node(state, conn_node);
if (panel_node < 0) {
printf("failed to find panel node\n");
return -ENODEV;
}
if (!fdt_device_is_available(blob, panel_node)) {
printf("panel is disabled\n");
return -ENODEV;
}
panel_state->node = panel_node;
panel = rockchip_get_panel(blob, panel_node);
if (!panel) {
printf("failed to find panel driver\n");
return 0;
}
panel_state->panel = panel;
ret = rockchip_panel_init(state);
if (ret) {
printf("failed to init panel driver\n");
return ret;
}
return 0;
}
- get_panel_node
这个函数就是去获取lvds_panel这个节点,原型:
static int get_panel_node(struct display_state *state, int conn_node)
{
const void *blob = state->blob;
int panel, ports, port, ep, remote, ph, nodedepth;
panel = fdt_subnode_offset(blob, conn_node, "panel");
if (panel > 0)
return panel;
ports = fdt_subnode_offset(blob, conn_node, "ports");
if (ports < 0)
return -ENODEV;
fdt_for_each_subnode(blob, port, ports) {
fdt_for_each_subnode(blob, ep, port) {
ph = fdt_getprop_u32_default_node(blob, ep, 0,
"remote-endpoint", 0);
if (!ph)
continue;
remote = fdt_node_offset_by_phandle(blob, ph);
nodedepth = fdt_node_depth(blob, remote);
if (nodedepth < 2)
continue;
panel = fdt_supernode_atdepth_offset(blob, remote,
nodedepth - 2,
NULL);
break;
}
}
return panel;
}
- rockchip_get_panel
这个函数是获取匹配属性,原型:
const struct rockchip_panel *rockchip_get_panel(const void *blob, int node)
{
const char *name;
int i;
fdt_get_string(blob, node, "compatible", &name);
for (i = 0; i < ARRAY_SIZE(g_panel); i++)
if (!strcmp(name, g_panel[i].compatible))
break;
if (i >= ARRAY_SIZE(g_panel))
return NULL;
return &g_panel[i];
}
- rockchip_panel_init
这个函数就会去初始化屏幕
3.
rockchip_panel_init原型如下:
int rockchip_panel_init(struct display_state *state)
{
struct panel_state *panel_state = &state->panel_state;
const struct rockchip_panel *panel = panel_state->panel;
printf("%s---------------------%d\r\n",__func__,__LINE__);
if (!panel || !panel->funcs || !panel->funcs->init) {
printf("%s: failed to find panel init funcs\n", __func__);
return -ENODEV;
}
return panel->funcs->init(state);
}
而在返回值这里使用了函数指针,panel->funcs->init(state);
而在这个const struct rockchip_panel *panel中有const struct rockchip_panel_funcs *funcs;这个成员变量,struct rockchip_panel 这个结构体内容如下:
struct rockchip_panel {
char compatible[30];
const struct rockchip_panel_funcs *funcs;
const void *data;
};
struct rockchip_panel_funcs 这个结构体的内容如下:
struct rockchip_panel_funcs {
int (*init)(struct display_state *state);
void (*deinit)(struct display_state *state);
int (*prepare)(struct display_state *state);
int (*unprepare)(struct display_state *state);
int (*enable)(struct display_state *state);
int (*disable)(struct display_state *state);
};
而这些函数的实现是在panel_simple.c中,其中init函数赋值如下:
const struct rockchip_panel_funcs panel_simple_funcs = {
.init = panel_simple_init,
.deinit = panel_simple_deinit,
.prepare = panel_simple_prepare,
.unprepare = panel_simple_unprepare,
.enable = panel_simple_enable,
.disable = panel_simple_disable,
};
实现如下:
struct fdt_gpio_state gpio_ctl[3];
static int panel_simple_init(struct display_state *state)
{
const void *blob = state->blob;
struct connector_state *conn_state = &state->conn_state;
struct panel_state *panel_state = &state->panel_state;
int node = panel_state->node;
const struct drm_display_mode *mode = panel_state->panel->data;
struct panel_simple *panel;
int ret;
printf("%s---------------------%d\r\n",__func__,__LINE__);
node = fdt_node_offset_by_compatible(blob,0, "simple-panel");
if (node < 0) {
printf("%s-->No detect Pine,mini_control.\n", __func__);
return -ENODEV;
}
if (!fdt_device_is_available(blob,node)) {
printf("%s-->device gpio_ctl is disabled\n", __func__);
return -1;
}
fdtdec_decode_gpios(blob, node, "power-gpios", &gpio_ctl[0], 1);
printf("%s:---------power_gpio:%d\r\n",__func__,gpio_ctl[0].gpio);
fdtdec_decode_gpios(blob, node, "enable-gpios", &gpio_ctl[1], 1);
printf("%s:---------enable_gpio:%d\r\n",__func__,gpio_ctl[1].gpio);
fdtdec_decode_gpios(blob, node, "reset-gpios", &gpio_ctl[2], 1);
printf("%s:---------reset_gpio:%d\r\n",__func__,gpio_ctl[2].gpio);
gpio_direction_output(gpio_ctl[0].gpio,(gpio_ctl[0].flags& OF_GPIO_ACTIVE_LOW));
gpio_direction_output(gpio_ctl[1].gpio,(gpio_ctl[1].flags& OF_GPIO_ACTIVE_LOW));
gpio_direction_output(gpio_ctl[2].gpio,(gpio_ctl[2].flags& OF_GPIO_ACTIVE_LOW));
msleep(1000);
gpio_direction_output(gpio_ctl[0].gpio,!(gpio_ctl[0].flags& OF_GPIO_ACTIVE_LOW));
msleep(10);
gpio_direction_output(gpio_ctl[1].gpio,!(gpio_ctl[1].flags& OF_GPIO_ACTIVE_LOW));
msleep(10);
gpio_direction_output(gpio_ctl[2].gpio,!(gpio_ctl[2].flags& OF_GPIO_ACTIVE_LOW));
panel = malloc(sizeof(*panel));
if (!panel)
return -ENOMEM;
panel->blob = blob;
panel->node = node;
panel->mode = mode;
panel_state->private = panel;
ret = panel_simple_parse_dt(blob, node, panel);
if (ret) {
printf("%s: failed to parse DT\n", __func__);
free(panel);
return ret;
}
conn_state->bus_format = panel->bus_format;
return 0;
}
在该函数中我是参考panel_simple_parse_dt这个函数去获取设备树中的IO属性的实现如下:
static int panel_simple_parse_dt(const void *blob, int node,
struct panel_simple *panel)
{
struct fdt_gpio_state *enable_gpio = &panel->enable_gpio;
fdtdec_decode_gpio(blob, node, "enable-gpios", enable_gpio);
panel->delay_prepare = fdtdec_get_int(blob, node, "delay,prepare", 0);
panel->delay_unprepare = fdtdec_get_int(blob, node, "delay,unprepare", 0);
panel->delay_enable = fdtdec_get_int(blob, node, "delay,enable", 0);
panel->delay_disable = fdtdec_get_int(blob, node, "delay,disable", 0);
panel->bus_format = fdtdec_get_int(blob, node, "bus-format", MEDIA_BUS_FMT_RBG888_1X24);
printf("delay prepare[%d] unprepare[%d] enable[%d] disable[%d]\n",
panel->delay_prepare, panel->delay_unprepare,
panel->delay_enable, panel->delay_disable);
/* keep panel blank on init. */
gpio_direction_output(enable_gpio->gpio,!(enable_gpio->flags & OF_GPIO_ACTIVE_LOW));
#ifdef CONFIG_RK_PWM_BL
rk_pwm_bl_config(0);
#endif
return 0;
}
fdtdec_decode_gpio该函数就是去获取IO口的,而这个struct fdt_gpio_state中的内容如下:
/* This is the state of a GPIO pin as defined by the fdt */
struct fdt_gpio_state {
const char *name; /* name of the fdt property defining this */
uint gpio; /* GPIO number, or FDT_GPIO_NONE if none */
u8 flags; /* FDT_GPIO_... flags */
};
用法就不说了上面有,控制输出高低:
gpio_direction_output(gpio_ctl[2].gpio,(gpio_ctl[2].flags& OF_GPIO_ACTIVE_LOW));
驱动很绕,贴代码不容易呀,想想写出这驱动的更不容易~_~