diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2015-08-07 07:55:00 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2015-08-07 07:55:00 +0200 |
commit | 98b5672c4d474f25227450a29f82f14e84c85f3c (patch) | |
tree | 38f074aca05571b2bed9295e6f83e098c350c94b | |
parent | 0c5c47b511763a2a9b216918e041178826fbdacc (diff) | |
parent | 427d637327bb4714085ea748bc5e7564ea0acfe6 (diff) | |
download | barebox-98b5672c4d474f25227450a29f82f14e84c85f3c.tar.gz barebox-98b5672c4d474f25227450a29f82f14e84c85f3c.tar.xz |
Merge branch 'for-next/vpl'
-rw-r--r-- | arch/arm/boards/efika-mx-smartbook/board.c | 3 | ||||
-rw-r--r-- | arch/arm/dts/imx51-genesi-efika-sb.dts | 130 | ||||
-rw-r--r-- | drivers/of/base.c | 206 | ||||
-rw-r--r-- | drivers/video/Kconfig | 21 | ||||
-rw-r--r-- | drivers/video/Makefile | 3 | ||||
-rw-r--r-- | drivers/video/imx-ipu-v3/Kconfig | 1 | ||||
-rw-r--r-- | drivers/video/imx-ipu-v3/imx-hdmi.c | 93 | ||||
-rw-r--r-- | drivers/video/imx-ipu-v3/imx-ipu-v3.h | 25 | ||||
-rw-r--r-- | drivers/video/imx-ipu-v3/imx-ldb.c | 116 | ||||
-rw-r--r-- | drivers/video/imx-ipu-v3/ipu-di.c | 2 | ||||
-rw-r--r-- | drivers/video/imx-ipu-v3/ipufb.c | 144 | ||||
-rw-r--r-- | drivers/video/mtl017.c | 280 | ||||
-rw-r--r-- | drivers/video/simple-panel.c | 160 | ||||
-rw-r--r-- | drivers/video/vpl.c | 115 | ||||
-rw-r--r-- | include/fb.h | 2 | ||||
-rw-r--r-- | include/of.h | 8 | ||||
-rw-r--r-- | include/of_graph.h | 50 | ||||
-rw-r--r-- | include/video/vpl.h | 46 |
18 files changed, 1179 insertions, 226 deletions
diff --git a/arch/arm/boards/efika-mx-smartbook/board.c b/arch/arm/boards/efika-mx-smartbook/board.c index 4a38afd27b..f023d70c28 100644 --- a/arch/arm/boards/efika-mx-smartbook/board.c +++ b/arch/arm/boards/efika-mx-smartbook/board.c @@ -187,6 +187,7 @@ static int efikamx_usb_init(void) gpio_direction_output(GPIO_WIFI_RESET, 0); gpio_direction_output(GPIO_SMSC3317_RESET, 0); gpio_direction_output(GPIO_HUB_RESET, 0); + gpio_direction_output(GPIO_BACKLIGHT_POWER, 1); mdelay(10); @@ -241,8 +242,6 @@ static int efikamx_late_init(void) defaultenv_append_directory(defaultenv_efikasb); - gpio_direction_output(GPIO_BACKLIGHT_POWER, 1); - for (i = 0; i < ARRAY_SIZE(leds); i++) led_gpio_register(&leds[i]); diff --git a/arch/arm/dts/imx51-genesi-efika-sb.dts b/arch/arm/dts/imx51-genesi-efika-sb.dts index 26829d14c0..78cb1b7184 100644 --- a/arch/arm/dts/imx51-genesi-efika-sb.dts +++ b/arch/arm/dts/imx51-genesi-efika-sb.dts @@ -90,12 +90,34 @@ mux-ext-port = <3>; }; - backlight { + backlight: backlight { compatible = "pwm-backlight"; + enable-gpios = <&gpio4 12 GPIO_ACTIVE_LOW>; pwms = <&pwm1 0 78770>; - brightness-levels = <0 4 8 16 32 64 128 255>; - default-brightness-level = <6>; - }; + brightness-levels = <0 1 2 4 8 16 32 64 128 255>; + default-brightness-level = <9>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_backlight>; + }; + + panel { + compatible = "simple-panel"; + backlight = <&backlight>; + enable-gpios = <&gpio3 13 GPIO_ACTIVE_HIGH>; + ddc-i2c-bus = <&i2c2>; + enable-delay = <200>; + + port { + panel_in: endpoint { + remote-endpoint = <&mtl017_out>; + }; + }; + }; + + lvds_reg: lvds_regulator { + compatible = "regulator-fixed"; + gpio = <&gpio3 7 GPIO_ACTIVE_HIGH>; + }; }; &ssi1 { @@ -115,16 +137,17 @@ MX51_PAD_DI1_PIN12__GPIO3_1 0x80000000 /* WLAN switch */ MX51_PAD_EIM_A17__GPIO2_11 0x80000000 /* Bluetooth power */ MX51_PAD_EIM_A23__GPIO2_17 0x80000000 /* Audio amp enable, 1 = on */ - MX51_PAD_CSI1_D8__GPIO3_12 0x80000000 /* LVDS enable, 1 = on */ - MX51_PAD_GPIO1_2__GPIO1_2 0x80000000 /* Backlight PWM */ - MX51_PAD_CSI2_D19__GPIO4_12 0x80000000 /* Backlight power, 0 = on */ - MX51_PAD_DISPB2_SER_DIN__GPIO3_5 0x80000000 /* LVDS reset (1 = reset) */ - MX51_PAD_DISPB2_SER_CLK__GPIO3_7 0x80000000 /* LVDS power, 1 = on */ - MX51_PAD_CSI1_D9__GPIO3_13 0x80000000 /* LCD enable (1 = on */ MX51_PAD_NANDF_CS0__GPIO3_16 0x80000000 /* Camera power, 0 = on */ MX51_PAD_GPIO1_5__GPIO1_5 0x80000000 /* USB hub reset, 0 = reset */ MX51_PAD_EIM_D27__GPIO2_9 0x80000000 /* USB phy reset, 0 = reset */ MX51_PAD_CSPI1_RDY__GPIO4_26 0x80000000 /* Audio clk enable */ + MX51_PAD_CSI2_D19__GPIO4_12 0x80000000 /* Backlight power, 0 = on */ + >; + }; + + pinctrl_backlight: backlightgrp { + fsl,pins = < + MX51_PAD_GPIO1_2__PWM1_PWMO 0x80000000 /* Backlight PWM */ >; }; @@ -187,34 +210,32 @@ >; }; - pinctrl_ipu_disp1: ipudisp1grp { + pinctrl_ipu_disp2: ipudisp2grp { fsl,pins = < - MX51_PAD_DISP1_DAT0__DISP1_DAT0 0x5 - MX51_PAD_DISP1_DAT1__DISP1_DAT1 0x5 - MX51_PAD_DISP1_DAT2__DISP1_DAT2 0x5 - MX51_PAD_DISP1_DAT3__DISP1_DAT3 0x5 - MX51_PAD_DISP1_DAT4__DISP1_DAT4 0x5 - MX51_PAD_DISP1_DAT5__DISP1_DAT5 0x5 - MX51_PAD_DISP1_DAT6__DISP1_DAT6 0x5 - MX51_PAD_DISP1_DAT7__DISP1_DAT7 0x5 - MX51_PAD_DISP1_DAT8__DISP1_DAT8 0x5 - MX51_PAD_DISP1_DAT9__DISP1_DAT9 0x5 - MX51_PAD_DISP1_DAT10__DISP1_DAT10 0x5 - MX51_PAD_DISP1_DAT11__DISP1_DAT11 0x5 - MX51_PAD_DISP1_DAT12__DISP1_DAT12 0x5 - MX51_PAD_DISP1_DAT13__DISP1_DAT13 0x5 - MX51_PAD_DISP1_DAT14__DISP1_DAT14 0x5 - MX51_PAD_DISP1_DAT15__DISP1_DAT15 0x5 - MX51_PAD_DISP1_DAT16__DISP1_DAT16 0x5 - MX51_PAD_DISP1_DAT17__DISP1_DAT17 0x5 - MX51_PAD_DISP1_DAT18__DISP1_DAT18 0x5 - MX51_PAD_DISP1_DAT19__DISP1_DAT19 0x5 - MX51_PAD_DISP1_DAT20__DISP1_DAT20 0x5 - MX51_PAD_DISP1_DAT21__DISP1_DAT21 0x5 - MX51_PAD_DISP1_DAT22__DISP1_DAT22 0x5 - MX51_PAD_DISP1_DAT23__DISP1_DAT23 0x5 - MX51_PAD_DI1_PIN2__DI1_PIN2 0x5 - MX51_PAD_DI1_PIN3__DI1_PIN3 0x5 + MX51_PAD_DISP2_DAT0__DISP2_DAT0 0x5 + MX51_PAD_DISP2_DAT1__DISP2_DAT1 0x5 + MX51_PAD_DISP2_DAT2__DISP2_DAT2 0x5 + MX51_PAD_DISP2_DAT3__DISP2_DAT3 0x5 + MX51_PAD_DISP2_DAT4__DISP2_DAT4 0x5 + MX51_PAD_DISP2_DAT5__DISP2_DAT5 0x5 + MX51_PAD_DISP2_DAT6__DISP2_DAT6 0x5 + MX51_PAD_DISP2_DAT7__DISP2_DAT7 0x5 + MX51_PAD_DISP2_DAT8__DISP2_DAT8 0x5 + MX51_PAD_DISP2_DAT9__DISP2_DAT9 0x5 + MX51_PAD_DISP2_DAT10__DISP2_DAT10 0x5 + MX51_PAD_DISP2_DAT11__DISP2_DAT11 0x5 + MX51_PAD_DISP2_DAT12__DISP2_DAT12 0x5 + MX51_PAD_DISP2_DAT13__DISP2_DAT13 0x5 + MX51_PAD_DISP2_DAT14__DISP2_DAT14 0x5 + MX51_PAD_DISP2_DAT15__DISP2_DAT15 0x5 + MX51_PAD_DI2_PIN2__DI2_PIN2 0x5 /* hsync */ + MX51_PAD_DI2_PIN3__DI2_PIN3 0x5 /* vsync */ + MX51_PAD_DI2_DISP_CLK__DI2_DISP_CLK 0x5 + MX51_PAD_DI_GP4__DI2_PIN15 0x5 + MX51_PAD_CSI1_D8__GPIO3_12 0x80000000 /* LVDS enable, 1 = on */ + MX51_PAD_DISPB2_SER_DIN__GPIO3_5 0x80000000 /* LVDS reset (1 = reset) */ + MX51_PAD_DISPB2_SER_CLK__GPIO3_7 0x80000000 /* LVDS power, 1 = on */ + MX51_PAD_CSI1_D9__GPIO3_13 0x80000000 /* LCD enable (1 = on */ >; }; @@ -342,13 +363,38 @@ }; lvds: mtl017@3a { + #address-cells = <1>; + #size-cells = <0>; compatible = "mtl017"; pinctrl-names = "default"; - pinctrl-0 = <&pinctrl_ipu_disp1>; + pinctrl-0 = <&pinctrl_ipu_disp2>; + enable-gpios = <&gpio3 12 GPIO_ACTIVE_HIGH>; + reset-gpios = <&gpio3 5 GPIO_ACTIVE_HIGH>; + vdd-supply = <&lvds_reg>; reg = <0x3a>; - crtcs = <&ipu 1>; - edid-i2c = <&i2c2>; - interface-pix-fmt = "rgb565"; + + port@0 { + reg = <0>; + + mtl017_in: endpoint { + remote-endpoint = <&ipu_di1_disp1>; + }; + }; + + port@1 { + reg = <1>; + + mtl017_out: endpoint { + remote-endpoint = <&panel_in>; + }; + }; + }; +}; + +&ipu_di1 { + interface-pix-fmt = "rgb565"; + endpoint { + remote-endpoint = <&mtl017_in>; }; }; @@ -371,7 +417,7 @@ partition@0 { label = "barebox-environment"; - reg = <0x80000 0x80000>; + reg = <0xc0000 0x40000>; }; }; diff --git a/drivers/of/base.c b/drivers/of/base.c index 7f35ee5b1f..d12bfe328a 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -25,6 +25,7 @@ #include <init.h> #include <memory.h> #include <linux/sizes.h> +#include <of_graph.h> #include <linux/ctype.h> #include <linux/amba/bus.h> #include <linux/err.h> @@ -1570,6 +1571,23 @@ struct device_node *of_get_next_available_child(const struct device_node *node, EXPORT_SYMBOL(of_get_next_available_child); /** + * of_get_next_child - Iterate a node childs + * @node: parent node + * @prev: previous child of the parent node, or NULL to get first + * + * Returns a node pointer with refcount incremented. + */ +struct device_node *of_get_next_child(const struct device_node *node, + struct device_node *prev) +{ + prev = list_prepare_entry(prev, &node->children, parent_list); + list_for_each_entry_continue(prev, &node->children, parent_list) + return prev; + return NULL; +} +EXPORT_SYMBOL(of_get_next_child); + +/** * of_get_child_count - Count child nodes of given parent node * @parent: parent node * @@ -2026,3 +2044,191 @@ int of_device_disable_path(const char *path) return of_device_disable(node); } + +/** + * of_graph_parse_endpoint() - parse common endpoint node properties + * @node: pointer to endpoint device_node + * @endpoint: pointer to the OF endpoint data structure + * + * The caller should hold a reference to @node. + */ +int of_graph_parse_endpoint(const struct device_node *node, + struct of_endpoint *endpoint) +{ + struct device_node *port_node = of_get_parent(node); + + if (!port_node) + pr_warn("%s(): endpoint %s has no parent node\n", + __func__, node->full_name); + + memset(endpoint, 0, sizeof(*endpoint)); + + endpoint->local_node = node; + /* + * It doesn't matter whether the two calls below succeed. + * If they don't then the default value 0 is used. + */ + of_property_read_u32(port_node, "reg", &endpoint->port); + of_property_read_u32(node, "reg", &endpoint->id); + + return 0; +} +EXPORT_SYMBOL(of_graph_parse_endpoint); + +/** + * of_graph_get_port_by_id() - get the port matching a given id + * @parent: pointer to the parent device node + * @id: id of the port + * + * Return: A 'port' node pointer with refcount incremented. + */ +struct device_node *of_graph_get_port_by_id(struct device_node *node, u32 id) +{ + struct device_node *port; + + for_each_child_of_node(node, port) { + u32 port_id = 0; + + if (strncmp(port->name, "port", 4) != 0) + continue; + of_property_read_u32(port, "reg", &port_id); + if (id == port_id) + return port; + } + + return NULL; +} +EXPORT_SYMBOL(of_graph_get_port_by_id); + +/** + * of_graph_get_next_endpoint() - get next endpoint node + * @parent: pointer to the parent device node + * @prev: previous endpoint node, or NULL to get first + * + * Return: An 'endpoint' node pointer with refcount incremented. Refcount + * of the passed @prev node is decremented. + */ +struct device_node *of_graph_get_next_endpoint(const struct device_node *parent, + struct device_node *prev) +{ + struct device_node *endpoint; + struct device_node *port; + + if (!parent) + return NULL; + + /* + * Start by locating the port node. If no previous endpoint is specified + * search for the first port node, otherwise get the previous endpoint + * parent port node. + */ + if (!prev) { + struct device_node *node; + + node = of_get_child_by_name(parent, "ports"); + if (node) + parent = node; + + port = of_get_child_by_name(parent, "port"); + if (!port) { + pr_err("%s(): no port node found in %s\n", + __func__, parent->full_name); + return NULL; + } + } else { + port = of_get_parent(prev); + if (!port) { + pr_warn("%s(): endpoint %s has no parent node\n", + __func__, prev->full_name); + return NULL; + } + } + + while (1) { + /* + * Now that we have a port node, get the next endpoint by + * getting the next child. If the previous endpoint is NULL this + * will return the first child. + */ + endpoint = of_get_next_child(port, prev); + if (endpoint) + return endpoint; + + /* No more endpoints under this port, try the next one. */ + prev = NULL; + + do { + port = of_get_next_child(parent, port); + if (!port) + return NULL; + } while (of_node_cmp(port->name, "port")); + } +} +EXPORT_SYMBOL(of_graph_get_next_endpoint); + +/** + * of_graph_get_remote_port_parent() - get remote port's parent node + * @node: pointer to a local endpoint device_node + * + * Return: Remote device node associated with remote endpoint node linked + * to @node. + */ +struct device_node *of_graph_get_remote_port_parent( + const struct device_node *node) +{ + struct device_node *np; + unsigned int depth; + + /* Get remote endpoint node. */ + np = of_parse_phandle(node, "remote-endpoint", 0); + + /* Walk 3 levels up only if there is 'ports' node. */ + for (depth = 3; depth && np; depth--) { + np = np->parent; + if (depth == 2 && of_node_cmp(np->name, "ports")) + break; + } + return np; +} +EXPORT_SYMBOL(of_graph_get_remote_port_parent); + +/** + * of_graph_get_remote_port() - get remote port node + * @node: pointer to a local endpoint device_node + * + * Return: Remote port node associated with remote endpoint node linked + * to @node. + */ +struct device_node *of_graph_get_remote_port(const struct device_node *node) +{ + struct device_node *np; + + /* Get remote endpoint node. */ + np = of_parse_phandle(node, "remote-endpoint", 0); + if (!np) + return NULL; + return np->parent; +} +EXPORT_SYMBOL(of_graph_get_remote_port); + +int of_graph_port_is_available(struct device_node *node) +{ + struct device_node *endpoint; + int available = 0; + + for_each_child_of_node(node, endpoint) { + struct device_node *remote_parent; + + remote_parent = of_graph_get_remote_port_parent(endpoint); + if (!remote_parent) + continue; + + if (!of_device_is_available(remote_parent)) + continue; + + available = 1; + } + + return available; +} +EXPORT_SYMBOL(of_graph_port_is_available); diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 85ba91d3c0..d7f5b07637 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -11,6 +11,9 @@ config FRAMEBUFFER_CONSOLE select FONTS prompt "framebuffer console support" +config VIDEO_VPL + bool + config DRIVER_VIDEO_ATMEL bool "Atmel LCDC framebuffer driver" depends on ARCH_AT91 @@ -111,4 +114,22 @@ config DRIVER_VIDEO_BACKLIGHT_PWM help Enable this to get support for backlight devices driven by a PWM. +comment "Video encoder chips" + +config DRIVER_VIDEO_MTL017 + bool "MTL017 LVDS encoder" + select VIDEO_VPL + help + The MTL017 is a parallel to lvds video encoder chip found on the + Efika MX Smartbook. + +config DRIVER_VIDEO_SIMPLE_PANEL + bool "Simple panel support" + select VIDEO_VPL + help + This enabled support for simple panels, i.e. panels which consist of + a mode definition and enable gpios in the devicetree. Unlike the + Linux Kernel implementation this one is able to understand display-timings + nodes so that it's not necessary to keep a list of all known displays + with their corresponding timings in barebox. endif diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 359135ea5e..57e4864a6d 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -4,6 +4,9 @@ obj-$(CONFIG_OFDEVICE) += of_display_timing.o obj-$(CONFIG_DRIVER_VIDEO_BACKLIGHT) += backlight.o obj-$(CONFIG_DRIVER_VIDEO_BACKLIGHT_PWM) += backlight-pwm.o obj-$(CONFIG_FRAMEBUFFER_CONSOLE) += fbconsole.o +obj-$(CONFIG_VIDEO_VPL) += vpl.o +obj-$(CONFIG_DRIVER_VIDEO_MTL017) += mtl017.o +obj-$(CONFIG_DRIVER_VIDEO_SIMPLE_PANEL) += simple-panel.o obj-$(CONFIG_DRIVER_VIDEO_ATMEL) += atmel_lcdfb.o atmel_lcdfb_core.o obj-$(CONFIG_DRIVER_VIDEO_ATMEL_HLCD) += atmel_hlcdfb.o atmel_lcdfb_core.o diff --git a/drivers/video/imx-ipu-v3/Kconfig b/drivers/video/imx-ipu-v3/Kconfig index 2ca9132f22..386ff5bc2e 100644 --- a/drivers/video/imx-ipu-v3/Kconfig +++ b/drivers/video/imx-ipu-v3/Kconfig @@ -1,6 +1,7 @@ config DRIVER_VIDEO_IMX_IPUV3 bool "i.MX IPUv3 driver" depends on ARCH_IMX + select VIDEO_VPL help Support the IPUv3 found on Freescale i.MX51/53/6 SoCs diff --git a/drivers/video/imx-ipu-v3/imx-hdmi.c b/drivers/video/imx-ipu-v3/imx-hdmi.c index e01bfe8f31..f5a2e3c601 100644 --- a/drivers/video/imx-ipu-v3/imx-hdmi.c +++ b/drivers/video/imx-ipu-v3/imx-hdmi.c @@ -23,6 +23,7 @@ #include <asm-generic/div64.h> #include <linux/clk.h> #include <i2c/i2c.h> +#include <video/vpl.h> #include <mach/imx6-regs.h> #include <mach/imx53-regs.h> @@ -133,13 +134,13 @@ struct imx_hdmi { bool phy_enabled; struct regmap *regmap; - struct i2c_adapter *ddc; + struct device_node *ddc_node;; void __iomem *regs; unsigned int sample_rate; int ratio; - struct ipu_output output; + struct vpl vpl; }; static void imx_hdmi_set_ipu_di_mux(struct imx_hdmi *hdmi, int ipu_di) @@ -1011,7 +1012,7 @@ static void imx_hdmi_clear_overflow(struct imx_hdmi *hdmi) hdmi_writeb(hdmi, val, HDMI_FC_INVIDCONF); } -static int imx_hdmi_setup(struct imx_hdmi *hdmi, struct fb_videomode *mode) +static int imx_hdmi_setup(struct imx_hdmi *hdmi) { int ret; @@ -1134,43 +1135,58 @@ static struct of_device_id imx_hdmi_dt_ids[] = { } }; -static int imx_hdmi_prepare(struct ipu_output *output, struct fb_videomode *mode, int di) +static int imx_hdmi_get_modes(struct imx_hdmi *hdmi, struct display_timings *timings) { - struct imx_hdmi *hdmi = container_of(output, struct imx_hdmi, output); + int ret = -ENOENT; - imx_hdmi_set_ipu_di_mux(hdmi, di); + if (hdmi->ddc_node) { + struct i2c_adapter *i2c; - return 0; -} + i2c = of_find_i2c_adapter_by_node(hdmi->ddc_node); + if (!i2c) + return -ENODEV; + timings->edid = edid_read_i2c(i2c); + if (!timings->edid) + return -EINVAL; -static int imx_hdmi_commit(struct ipu_output *output, struct fb_videomode *mode, int di) -{ - struct imx_hdmi *hdmi = container_of(output, struct imx_hdmi, output); - - imx_hdmi_setup(hdmi, mode); + ret = edid_to_display_timings(timings, timings->edid); + } - return 0; + return ret; } -static int imx_hdmi_disable(struct ipu_output *output) +static int imx_hdmi_ioctl(struct vpl *vpl, unsigned int port, + unsigned int cmd, void *data) { - struct imx_hdmi *hdmi = container_of(output, struct imx_hdmi, output); - - imx_hdmi_phy_disable(hdmi); + struct imx_hdmi *hdmi = container_of(vpl, struct imx_hdmi, vpl); + struct ipu_di_mode *mode; + + switch (cmd) { + case VPL_ENABLE: + return imx_hdmi_setup(hdmi); + case VPL_DISABLE: + imx_hdmi_phy_disable(hdmi); + return 0; + case VPL_PREPARE: + imx_hdmi_set_ipu_di_mux(hdmi, port); + return 0; + case VPL_GET_VIDEOMODES: + return imx_hdmi_get_modes(hdmi, data); + case IMX_IPU_VPL_DI_MODE: + mode = data; + + mode->di_clkflags = IPU_DI_CLKMODE_EXT | IPU_DI_CLKMODE_SYNC; + mode->interface_pix_fmt = V4L2_PIX_FMT_RGB24; + + return 0; + } return 0; } -static struct ipu_output_ops imx_hdmi_ops = { - .prepare = imx_hdmi_prepare, - .enable = imx_hdmi_commit, - .disable = imx_hdmi_disable, -}; - static int imx_hdmi_probe(struct device_d *dev) { struct device_node *np = dev->device_node; - struct device_node *ddc_node; struct imx_hdmi *hdmi; int ret; const struct imx_hdmi_data *devtype; @@ -1190,18 +1206,7 @@ static int imx_hdmi_probe(struct device_d *dev) if (ret) return ret; - if (IS_ENABLED(CONFIG_DRIVER_VIDEO_EDID)) { - ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0); - if (ddc_node) { - hdmi->ddc = of_find_i2c_adapter_by_node(ddc_node); - if (!hdmi->ddc) - dev_dbg(hdmi->dev, "failed to read ddc node\n"); - } else { - dev_dbg(hdmi->dev, "no ddc property found\n"); - } - - ddc_node = NULL; - } + hdmi->ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0); hdmi->regs = dev_request_mem_region(dev, 0); if (!hdmi->regs) @@ -1269,15 +1274,11 @@ static int imx_hdmi_probe(struct device_d *dev) /* Unmute interrupts */ hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0); - hdmi->output.ops = &imx_hdmi_ops; - hdmi->output.di_clkflags = IPU_DI_CLKMODE_EXT | IPU_DI_CLKMODE_SYNC; - hdmi->output.out_pixel_fmt = V4L2_PIX_FMT_RGB24; - hdmi->output.name = asprintf("hdmi-0"); - hdmi->output.ipu_mask = devtype->ipu_mask; - hdmi->output.edid_i2c_adapter = hdmi->ddc; - hdmi->output.modes = of_get_display_timings(np); - - ipu_register_output(&hdmi->output); + hdmi->vpl.node = np; + hdmi->vpl.ioctl = imx_hdmi_ioctl; + ret = vpl_register(&hdmi->vpl); + if (ret) + return ret; return 0; diff --git a/drivers/video/imx-ipu-v3/imx-ipu-v3.h b/drivers/video/imx-ipu-v3/imx-ipu-v3.h index 7c48a7ce38..783535ea54 100644 --- a/drivers/video/imx-ipu-v3/imx-ipu-v3.h +++ b/drivers/video/imx-ipu-v3/imx-ipu-v3.h @@ -14,6 +14,7 @@ #include <io.h> #include <fb.h> +#include <video/vpl.h> #include <video/fourcc.h> struct ipu_soc; @@ -44,7 +45,6 @@ struct ipu_di_signal_cfg { u16 width; u16 height; - u32 pixel_fmt; u16 h_start_width; u16 h_sync_width; u16 h_end_width; @@ -319,26 +319,11 @@ struct ipu_client_platformdata { struct device_node *device_node; }; -struct ipu_output; - -struct ipu_output_ops { - int (*prepare)(struct ipu_output *ipu_video_output, struct fb_videomode *mode, int di); - int (*enable)(struct ipu_output *ipu_video_output, struct fb_videomode *mode, int di); - int (*disable)(struct ipu_output *ipu_video_output); - int (*unprepare)(struct ipu_output *ipu_video_output); -}; - -struct ipu_output { - struct ipu_output_ops *ops; - struct list_head list; - unsigned int di_clkflags; - uint32_t out_pixel_fmt; - struct i2c_adapter *edid_i2c_adapter; - struct display_timings *modes; - char *name; - int ipu_mask; +struct ipu_di_mode { + u32 di_clkflags; + u32 interface_pix_fmt; }; -int ipu_register_output(struct ipu_output *ouput); +#define IMX_IPU_VPL_DI_MODE 0x12660001 #endif /* __DRM_IPU_H__ */ diff --git a/drivers/video/imx-ipu-v3/imx-ldb.c b/drivers/video/imx-ipu-v3/imx-ldb.c index a05bfad8c0..db5f8b631d 100644 --- a/drivers/video/imx-ipu-v3/imx-ldb.c +++ b/drivers/video/imx-ipu-v3/imx-ldb.c @@ -21,10 +21,13 @@ #include <common.h> #include <fb.h> #include <io.h> +#include <of_graph.h> #include <driver.h> #include <malloc.h> #include <errno.h> #include <init.h> +#include <video/vpl.h> +#include <mfd/imx6q-iomuxc-gpr.h> #include <linux/clk.h> #include <linux/err.h> #include <asm-generic/div64.h> @@ -57,13 +60,17 @@ struct imx_ldb_channel { int chno; int mode_valid; struct display_timings *modes; - struct ipu_output output; + struct device_node *remote; + struct vpl vpl; + int output_port; + int datawidth; }; struct imx_ldb_data { void __iomem *base; int (*prepare)(struct imx_ldb_channel *imx_ldb_ch, int di); unsigned ipu_mask; + int have_mux; }; struct imx_ldb { @@ -102,16 +109,11 @@ static const int of_get_data_mapping(struct device_node *np) return -EINVAL; } -static int imx_ldb_prepare(struct ipu_output *output, struct fb_videomode *mode, int di) +static int imx_ldb_prepare(struct imx_ldb_channel *imx_ldb_ch, struct fb_videomode *mode, + int di) { - struct imx_ldb_channel *imx_ldb_ch = container_of(output, struct imx_ldb_channel, output); struct imx_ldb *ldb = imx_ldb_ch->ldb; - if (PICOS2KHZ(mode->pixclock) > 85000) { - dev_warn(ldb->dev, - "%s: mode exceeds 85 MHz pixel clock\n", __func__); - } - ldb->soc_data->prepare(imx_ldb_ch, di); /* FIXME - assumes straight connections DI0 --> CH0, DI1 --> CH1 */ @@ -141,7 +143,20 @@ static int imx_ldb_prepare(struct ipu_output *output, struct fb_videomode *mode, writel(ldb->ldb_ctrl, ldb->base); - return 0; + return vpl_ioctl(&imx_ldb_ch->vpl, imx_ldb_ch->output_port, + VPL_PREPARE, NULL); +} + +static int imx_ldb_enable(struct imx_ldb_channel *imx_ldb_ch, int di) +{ + return vpl_ioctl(&imx_ldb_ch->vpl, imx_ldb_ch->output_port, + VPL_ENABLE, NULL); +} + +static int imx_ldb_disable(struct imx_ldb_channel *imx_ldb_ch, int di) +{ + return vpl_ioctl(&imx_ldb_ch->vpl, imx_ldb_ch->output_port, + VPL_DISABLE, NULL); } static int imx6q_ldb_prepare(struct imx_ldb_channel *imx_ldb_ch, int di) @@ -226,6 +241,7 @@ static struct imx_ldb_data imx_ldb_data_imx6q = { .base = (void *)MX6_IOMUXC_BASE_ADDR + 0x8, .prepare = imx6q_ldb_prepare, .ipu_mask = 0xf, + .have_mux = 1, }; static struct imx_ldb_data imx_ldb_data_imx53 = { @@ -234,9 +250,42 @@ static struct imx_ldb_data imx_ldb_data_imx53 = { .ipu_mask = 0x3, }; -static struct ipu_output_ops imx_ldb_ops = { - .prepare = imx_ldb_prepare, -}; +static int imx_ldb_ioctl(struct vpl *vpl, unsigned int port, + unsigned int cmd, void *data) +{ + struct imx_ldb_channel *imx_ldb_ch = container_of(vpl, + struct imx_ldb_channel, vpl); + int ret; + struct ipu_di_mode *mode; + + switch (cmd) { + case VPL_ENABLE: + ret = vpl_ioctl(vpl, imx_ldb_ch->output_port, cmd, data); + if (ret) + return ret; + return imx_ldb_enable(imx_ldb_ch, port); + case VPL_DISABLE: + ret = vpl_ioctl(vpl, imx_ldb_ch->output_port, cmd, data); + if (ret) + return ret; + return imx_ldb_disable(imx_ldb_ch, port); + case VPL_PREPARE: + ret = vpl_ioctl(vpl, imx_ldb_ch->output_port, cmd, data); + if (ret) + return ret; + return imx_ldb_prepare(imx_ldb_ch, data, port); + case IMX_IPU_VPL_DI_MODE: + mode = data; + + mode->di_clkflags = IPU_DI_CLKMODE_EXT | IPU_DI_CLKMODE_SYNC; + mode->interface_pix_fmt = (imx_ldb_ch->datawidth == 24) ? + V4L2_PIX_FMT_RGB24 : V4L2_PIX_FMT_BGR666; + + return 0; + default: + return vpl_ioctl(vpl, imx_ldb_ch->output_port, cmd, data); + } +} static int imx_ldb_probe(struct device_d *dev) { @@ -245,7 +294,6 @@ static int imx_ldb_probe(struct device_d *dev) struct imx_ldb *imx_ldb; int ret, i; int dual = 0; - int datawidth; int mapping; const struct imx_ldb_data *devtype; @@ -259,6 +307,8 @@ static int imx_ldb_probe(struct device_d *dev) for_each_child_of_node(np, child) { struct imx_ldb_channel *channel; + struct device_node *port; + struct device_node *endpoint; ret = of_property_read_u32(child, "reg", &i); if (ret || i < 0 || i > 1) @@ -275,17 +325,37 @@ static int imx_ldb_probe(struct device_d *dev) channel = &imx_ldb->channel[i]; channel->ldb = imx_ldb; channel->chno = i; + channel->output_port = imx_ldb->soc_data->have_mux ? 4 : 1; + + /* The output port is port@4 with mux or port@1 without mux */ + port = of_graph_get_port_by_id(child, channel->output_port); + if (!port) { + dev_warn(dev, "No port found for %s\n", child->full_name); + continue; + } - ret = of_property_read_u32(child, "fsl,data-width", &datawidth); + endpoint = of_get_child_by_name(port, "endpoint"); + if (!endpoint) { + dev_warn(dev, "No endpoint found on %s\n", port->full_name); + continue; + } + + channel->vpl.node = child; + channel->vpl.ioctl = &imx_ldb_ioctl; + ret = vpl_register(&channel->vpl); + if (ret) + return ret; + + ret = of_property_read_u32(child, "fsl,data-width", &channel->datawidth); if (ret) - datawidth = 0; - else if (datawidth != 18 && datawidth != 24) + channel->datawidth = 0; + else if (channel->datawidth != 18 && channel->datawidth != 24) return -EINVAL; mapping = of_get_data_mapping(child); switch (mapping) { case LVDS_BIT_MAP_SPWG: - if (datawidth == 24) { + if (channel->datawidth == 24) { if (i == 0 || dual) imx_ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24; if (i == 1 || dual) @@ -293,7 +363,7 @@ static int imx_ldb_probe(struct device_d *dev) } break; case LVDS_BIT_MAP_JEIDA: - if (datawidth == 18) { + if (channel->datawidth == 18) { dev_err(dev, "JEIDA standard only supported in 24 bit\n"); return -EINVAL; } @@ -306,16 +376,6 @@ static int imx_ldb_probe(struct device_d *dev) dev_err(dev, "data mapping not specified or invalid\n"); return -EINVAL; } - - channel->output.ops = &imx_ldb_ops; - channel->output.di_clkflags = IPU_DI_CLKMODE_EXT | IPU_DI_CLKMODE_SYNC; - channel->output.out_pixel_fmt = (datawidth == 24) ? - V4L2_PIX_FMT_RGB24 : V4L2_PIX_FMT_BGR666; - channel->output.modes = of_get_display_timings(child); - channel->output.name = asprintf("ldb-%d", i); - channel->output.ipu_mask = devtype->ipu_mask; - - ipu_register_output(&channel->output); } return 0; diff --git a/drivers/video/imx-ipu-v3/ipu-di.c b/drivers/video/imx-ipu-v3/ipu-di.c index e3338d09ba..8df9c9f06b 100644 --- a/drivers/video/imx-ipu-v3/ipu-di.c +++ b/drivers/video/imx-ipu-v3/ipu-di.c @@ -627,7 +627,7 @@ int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig) } } - if (!sig->clk_pol) + if (sig->clk_pol) di_gen |= DI_GEN_POLARITY_DISP_CLK; ipu_di_write(di, di_gen, DI_GENERAL); diff --git a/drivers/video/imx-ipu-v3/ipufb.c b/drivers/video/imx-ipu-v3/ipufb.c index 7ee4ae3627..e804c31b29 100644 --- a/drivers/video/imx-ipu-v3/ipufb.c +++ b/drivers/video/imx-ipu-v3/ipufb.c @@ -19,6 +19,7 @@ #include <malloc.h> #include <errno.h> #include <init.h> +#include <of_graph.h> #include <linux/clk.h> #include <linux/err.h> #include <asm-generic/div64.h> @@ -62,8 +63,9 @@ struct ipufb_info { struct list_head list; char *name; int id; + int dino; - struct ipu_output *output; + struct vpl vpl; }; static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) @@ -73,23 +75,6 @@ static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) return chan << bf->offset; } -static LIST_HEAD(ipu_outputs); -static LIST_HEAD(ipu_fbs); - -int ipu_register_output(struct ipu_output *ouput) -{ - list_add_tail(&ouput->list, &ipu_outputs); - - return 0; -} - -static int ipu_register_fb(struct ipufb_info *ipufb) -{ - list_add_tail(&ipufb->list, &ipu_fbs); - - return 0; -} - int ipu_crtc_mode_set(struct ipufb_info *fbi, struct fb_videomode *mode, int x, int y) @@ -97,14 +82,17 @@ int ipu_crtc_mode_set(struct ipufb_info *fbi, struct fb_info *info = &fbi->info; int ret; struct ipu_di_signal_cfg sig_cfg = {}; - u32 out_pixel_fmt; + struct ipu_di_mode di_mode = {}; + u32 interface_pix_fmt; dev_info(fbi->dev, "%s: mode->xres: %d\n", __func__, mode->xres); dev_info(fbi->dev, "%s: mode->yres: %d\n", __func__, mode->yres); - out_pixel_fmt = fbi->output->out_pixel_fmt; + vpl_ioctl(&fbi->vpl, 2 + fbi->dino, IMX_IPU_VPL_DI_MODE, &di_mode); + interface_pix_fmt = di_mode.interface_pix_fmt ? + di_mode.interface_pix_fmt : fbi->interface_pix_fmt; if (mode->sync & FB_SYNC_HOR_HIGH_ACT) sig_cfg.Hsync_pol = 1; @@ -112,10 +100,9 @@ int ipu_crtc_mode_set(struct ipufb_info *fbi, sig_cfg.Vsync_pol = 1; sig_cfg.enable_pol = 1; - sig_cfg.clk_pol = 1; + sig_cfg.clk_pol = 0; sig_cfg.width = mode->xres; sig_cfg.height = mode->yres; - sig_cfg.pixel_fmt = out_pixel_fmt; sig_cfg.h_start_width = mode->left_margin; sig_cfg.h_sync_width = mode->hsync_len; sig_cfg.h_end_width = mode->right_margin; @@ -124,7 +111,7 @@ int ipu_crtc_mode_set(struct ipufb_info *fbi, sig_cfg.v_sync_width = mode->vsync_len; sig_cfg.v_end_width = mode->lower_margin; sig_cfg.pixelclock = PICOS2KHZ(mode->pixclock) * 1000UL; - sig_cfg.clkflags = fbi->output->di_clkflags; + sig_cfg.clkflags = di_mode.di_clkflags; sig_cfg.v_to_h_sync = 0; @@ -132,7 +119,7 @@ int ipu_crtc_mode_set(struct ipufb_info *fbi, sig_cfg.vsync_pin = 3; ret = ipu_dc_init_sync(fbi->dc, fbi->di, sig_cfg.interlaced, - out_pixel_fmt, mode->xres); + interface_pix_fmt, mode->xres); if (ret) { dev_err(fbi->dev, "initializing display controller failed with %d\n", @@ -171,31 +158,25 @@ int ipu_crtc_mode_set(struct ipufb_info *fbi, static void ipufb_enable_controller(struct fb_info *info) { struct ipufb_info *fbi = container_of(info, struct ipufb_info, info); - struct ipu_output *output = fbi->output; - if (output->ops->prepare) - output->ops->prepare(output, info->mode, fbi->id); + vpl_ioctl_prepare(&fbi->vpl, 2 + fbi->dino, info->mode); ipu_crtc_mode_set(fbi, info->mode, 0, 0); - if (output->ops->enable) - output->ops->enable(output, info->mode, fbi->id); + vpl_ioctl_enable(&fbi->vpl, 2 + fbi->dino); } static void ipufb_disable_controller(struct fb_info *info) { struct ipufb_info *fbi = container_of(info, struct ipufb_info, info); - struct ipu_output *output = fbi->output; - if (output->ops->disable) - output->ops->disable(output); + vpl_ioctl_disable(&fbi->vpl, 2 + fbi->dino); ipu_plane_disable(fbi->plane[0]); ipu_dc_disable_channel(fbi->dc); ipu_di_disable(fbi->di); - if (output->ops->unprepare) - output->ops->unprepare(output); + vpl_ioctl_unprepare(&fbi->vpl, 2 + fbi->dino); } static int ipufb_activate_var(struct fb_info *info) @@ -208,7 +189,7 @@ static int ipufb_activate_var(struct fb_info *info) if (!fbi->info.screen_base) return -ENOMEM; - memset(fbi->info.screen_base, 0, info->line_length * info->yres); + memset(fbi->info.screen_base, 0x0, info->line_length * info->yres); return 0; } @@ -219,56 +200,6 @@ static struct fb_ops ipufb_ops = { .fb_activate_var = ipufb_activate_var, }; -static struct ipufb_info *ipu_output_find_di(struct ipu_output *output) -{ - struct ipufb_info *ipufb; - - list_for_each_entry(ipufb, &ipu_fbs, list) { - if (!(output->ipu_mask & (1 << ipufb->id))) - continue; - if (ipufb->output) - continue; - - return ipufb; - } - - return NULL; -} - -static int ipu_init(void) -{ - struct ipu_output *output; - struct ipufb_info *ipufb; - int ret; - - list_for_each_entry(output, &ipu_outputs, list) { - pr_info("found output: %s\n", output->name); - ipufb = ipu_output_find_di(output); - if (!ipufb) { - pr_info("no di found for output %s\n", output->name); - continue; - } - pr_info("using di %s for output %s\n", ipufb->name, output->name); - - ipufb->output = output; - - ipufb->info.edid_i2c_adapter = output->edid_i2c_adapter; - if (output->modes) { - ipufb->info.modes.modes = output->modes->modes; - ipufb->info.modes.num_modes = output->modes->num_modes; - } - - ret = register_framebuffer(&ipufb->info); - if (ret < 0) { - dev_err(ipufb->dev, "failed to register framebuffer\n"); - return ret; - } - } - - return 0; -} -late_initcall(ipu_init); - static int ipu_get_resources(struct ipufb_info *fbi, struct ipu_client_platformdata *pdata) { @@ -311,6 +242,8 @@ static int ipufb_probe(struct device_d *dev) int ret, ipuid; struct ipu_client_platformdata *pdata = dev->platform_data; struct ipu_rgb *ipu_rgb; + struct device_node *node; + const char *fmt; fbi = xzalloc(sizeof(*fbi)); info = &fbi->info; @@ -318,6 +251,7 @@ static int ipufb_probe(struct device_d *dev) ipuid = of_alias_get_id(dev->parent->device_node, "ipu"); fbi->name = asprintf("ipu%d-di%d", ipuid + 1, pdata->di); fbi->id = ipuid * 2 + pdata->di; + fbi->dino = pdata->di; fbi->dev = dev; info->priv = fbi; @@ -337,7 +271,38 @@ static int ipufb_probe(struct device_d *dev) if (ret) return ret; - ret = ipu_register_fb(fbi); + node = of_graph_get_port_by_id(dev->parent->device_node, 2 + pdata->di); + if (node && of_graph_port_is_available(node)) { + dev_info(fbi->dev, "register vpl for %s\n", dev->parent->device_node->full_name); + + fbi->vpl.node = dev->parent->device_node; + ret = vpl_register(&fbi->vpl); + if (ret) + return ret; + + ret = of_property_read_string(node, "interface-pix-fmt", &fmt); + if (!ret) { + if (!strcmp(fmt, "rgb24")) + fbi->interface_pix_fmt = V4L2_PIX_FMT_RGB24; + else if (!strcmp(fmt, "rgb565")) + fbi->interface_pix_fmt = V4L2_PIX_FMT_RGB565; + else if (!strcmp(fmt, "bgr666")) + fbi->interface_pix_fmt = V4L2_PIX_FMT_BGR666; + else if (!strcmp(fmt, "lvds666")) + fbi->interface_pix_fmt = + v4l2_fourcc('L', 'V', 'D', '6'); + } + + ret = vpl_ioctl(&fbi->vpl, 2 + fbi->dino, VPL_GET_VIDEOMODES, &info->modes); + if (ret) + return ret; + + ret = register_framebuffer(info); + if (ret < 0) { + dev_err(fbi->dev, "failed to register framebuffer\n"); + return ret; + } + } return ret; } @@ -351,4 +316,9 @@ static struct driver_d ipufb_driver = { .probe = ipufb_probe, .remove = ipufb_remove, }; -device_platform_driver(ipufb_driver); + +static int ipufb_register(void) +{ + return platform_driver_register(&ipufb_driver); +} +late_initcall(ipufb_register); diff --git a/drivers/video/mtl017.c b/drivers/video/mtl017.c new file mode 100644 index 0000000000..1a1f686223 --- /dev/null +++ b/drivers/video/mtl017.c @@ -0,0 +1,280 @@ +/* + * (C) Copyright 2014 Sascha Hauer, Pengutronix + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <common.h> +#include <init.h> +#include <driver.h> +#include <xfuncs.h> +#include <errno.h> +#include <regulator.h> +#include <of_gpio.h> +#include <gpio.h> +#include <fb.h> +#include <video/vpl.h> + +#include <i2c/i2c.h> + +struct mtl017 { + struct vpl vpl; + struct device_d *dev; + struct i2c_client *client; + u8 *regs; + int enable_gpio; + int enable_active_high; + int reset_gpio; + int reset_active_high; + struct regulator *regulator; +}; + +/* + * Unfortunately we know nothing about the mtl017 except the following + * register tables which are derived from the Efika kernel. The displays + * provide EDID data, but this does not work with the mtl017 or at least + * not with the below register settings, so we have to provide hardcoded + * modelines and use the EDID data only to match against the vendor/display. + */ +static u8 mtl017_44_54_tbl[] = { + /* 44M to 53.9M */ + 0x00, 0x20, 0xAF, 0x59, 0x2B, 0xDE, 0x51, 0x00, + 0x00, 0x04, 0x17, 0x00, 0x58, 0x02, 0x00, 0x00, + 0x00, 0x3B, 0x01, 0x08, 0x00, 0x1E, 0x01, 0x05, + 0x00, 0x01, 0x72, 0x05, 0x32, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x20, 0xA8, 0x02, 0x12, 0x00, 0x58, + 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x02, 0x10, 0x01, 0x68, 0x03, 0xC2, 0x01, + 0x4A, 0x03, 0x46, 0x00, 0xF1, 0x01, 0x5C, 0x04, + 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x3A, + 0x18, 0x4B, 0x29, 0x5C, 0xDE, 0xF6, 0xE0, 0x1C, + 0x03, 0xFC, 0xE3, 0x1F, 0xF3, 0x75, 0x26, 0x45, + 0x4A, 0x91, 0x8A, 0xFF, 0x3F, 0x83, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x4E, 0x48, + 0x00, 0x01, 0x10, 0x01, 0x00, 0x00, 0x10, 0x04, + 0x02, 0x1F, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x04, 0x12, 0x00, 0x58, 0x02, + 0x02, 0x7C, 0x04, 0x98, 0x02, 0x11, 0x78, 0x18, + 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +}; + +#define REGMAP_LENGTH (sizeof(mtl017_44_54_tbl) / sizeof(u8)) +#define BLOCK_TX_SIZE 32 + +struct mtl017_panel_info { + char *manufacturer; + char *product_name; + struct fb_videomode *mode; + u8 *regs; +}; + + +static struct fb_videomode auo_bw101aw02_mode = { + .name = "AUO B101AW02 1024x600", + .refresh = 60, + .xres = 1024, + .yres = 600, + .pixclock = 22800, + .left_margin = 80, + .right_margin = 40, + .upper_margin = 21, + .lower_margin = 21, + .hsync_len = 4, + .vsync_len = 4, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static struct mtl017_panel_info panels[] = { + { + .manufacturer = "AUO", + .product_name = "B101AW02 V0", + .mode = &auo_bw101aw02_mode, + .regs = mtl017_44_54_tbl, + }, + { + .manufacturer = "CMO", + .product_name = "N101L6-L0D", + .mode = &auo_bw101aw02_mode, + .regs = mtl017_44_54_tbl, + }, +}; + +static int mtl017_get_videomodes(struct mtl017 *mtl017, struct display_timings *timings) +{ + int ret, i; + + ret = vpl_ioctl(&mtl017->vpl, 1, VPL_GET_VIDEOMODES, timings); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(panels); i ++) { + if (memcmp(timings->edid + 0x71, panels[i].product_name, + strlen(panels[i].product_name))) + continue; + + dev_dbg(mtl017->dev, "found LCD Panel: %s %s\n", + panels[i].manufacturer, + panels[i].product_name); + mtl017->regs = panels[i].regs; + + timings->modes[0].name = panels[i].mode->name; + timings->modes[0].refresh = panels[i].mode->refresh; + timings->modes[0].xres = panels[i].mode->xres; + timings->modes[0].yres = panels[i].mode->yres; + timings->modes[0].pixclock = panels[i].mode->pixclock; + timings->modes[0].left_margin = panels[i].mode->left_margin; + timings->modes[0].right_margin = panels[i].mode->right_margin; + timings->modes[0].upper_margin = panels[i].mode->upper_margin; + timings->modes[0].lower_margin = panels[i].mode->lower_margin; + timings->modes[0].hsync_len = panels[i].mode->hsync_len; + timings->modes[0].vsync_len = panels[i].mode->vsync_len; + timings->num_modes = 1; + } + + return 0; +} + +static int mtl017_enable(struct mtl017 *mtl017) +{ + int i; + int ret; + int retry = 5; + u8 *reg_tbl = mtl017->regs; + + dev_dbg(mtl017->dev, "Initializing MTL017 LVDS Controller\n"); + + ret = regulator_enable(mtl017->regulator); + if (ret) + return ret; + + gpio_direction_output(mtl017->reset_gpio, mtl017->reset_active_high); + mdelay(5); + gpio_direction_output(mtl017->reset_gpio, !mtl017->reset_active_high); + mdelay(5); + gpio_direction_output(mtl017->enable_gpio, mtl017->enable_active_high); + + /* Write configuration table */ + for (i = 0; i < REGMAP_LENGTH; i+=BLOCK_TX_SIZE) { +retry: + mdelay(1); + ret = i2c_smbus_write_i2c_block_data(mtl017->client, i, BLOCK_TX_SIZE, &(reg_tbl[i])); + if (ret < 0) { + dev_warn(mtl017->dev, "failed to initialize: %d\n", ret); + if (retry-- > 0) + goto retry; + return -EIO; + } + } + + return 0; +} + +static int mtl017_ioctl(struct vpl *vpl, unsigned int port, + unsigned int cmd, void *ptr) +{ + struct mtl017 *mtl017 = container_of(vpl, + struct mtl017, vpl); + int ret = 0; + + switch (cmd) { + case VPL_PREPARE: + dev_dbg(mtl017->dev, "VPL_PREPARE\n"); + goto forward; + case VPL_ENABLE: + dev_dbg(mtl017->dev, "VPL_ENABLE\n"); + + ret = mtl017_enable(mtl017); + if (ret < 0) + break; + + goto forward; + case VPL_DISABLE: + dev_dbg(mtl017->dev, "VPL_DISABLE\n"); + + goto forward; + case VPL_GET_VIDEOMODES: + dev_dbg(mtl017->dev, "VPL_GET_VIDEOMODES\n"); + + ret = mtl017_get_videomodes(mtl017, ptr); + break; + default: + break; + } + + return ret; + +forward: + return vpl_ioctl(&mtl017->vpl, 1, cmd, ptr); +} + +static int mtl017_probe(struct device_d *dev) +{ + struct mtl017 *mtl017; + int ret; + enum of_gpio_flags flags; + + mtl017 = xzalloc(sizeof(struct mtl017)); + mtl017->vpl.node = dev->device_node; + mtl017->vpl.ioctl = mtl017_ioctl; + mtl017->dev = dev; + mtl017->client = to_i2c_client(dev); + mtl017->regulator = regulator_get(dev, "vdd"); + + mtl017->enable_gpio = of_get_named_gpio_flags(dev->device_node, + "enable-gpios", 0, &flags); + if (gpio_is_valid(mtl017->enable_gpio)) { + if (!(flags & OF_GPIO_ACTIVE_LOW)) + mtl017->enable_active_high = 1; + } + + mtl017->reset_gpio = of_get_named_gpio_flags(dev->device_node, + "reset-gpios", 0, &flags); + if (gpio_is_valid(mtl017->reset_gpio)) { + if (!(flags & OF_GPIO_ACTIVE_LOW)) + mtl017->reset_active_high = 1; + } + + ret = vpl_register(&mtl017->vpl); + if (ret) + return ret; + + return 0; +} + +static struct driver_d twl_driver = { + .name = "mtl017", + .probe = mtl017_probe, +}; + +static int mtl017_init(void) +{ + i2c_driver_register(&twl_driver); + return 0; +} + +device_initcall(mtl017_init); diff --git a/drivers/video/simple-panel.c b/drivers/video/simple-panel.c new file mode 100644 index 0000000000..dceedc60c3 --- /dev/null +++ b/drivers/video/simple-panel.c @@ -0,0 +1,160 @@ +/* + * simple panel support for barebox + * + * (C) Copyright 2014 Sascha Hauer, Pengutronix + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <common.h> +#include <malloc.h> +#include <init.h> +#include <linux/err.h> +#include <of.h> +#include <fb.h> +#include <gpio.h> +#include <of_gpio.h> +#include <video/backlight.h> +#include <video/vpl.h> +#include <i2c/i2c.h> + +struct simple_panel { + struct vpl vpl; + int enable_gpio; + int enable_active_high; + int enabled; + struct device_node *backlight_node; + struct backlight_device *backlight; + struct device_node *ddc_node; + int enable_delay; +}; + +static int simple_panel_enable(struct simple_panel *panel) +{ + int ret; + + if (panel->backlight_node && !panel->backlight) { + panel->backlight = of_backlight_find(panel->backlight_node); + if (!panel->backlight) + return -ENODEV; + } + + if (gpio_is_valid(panel->enable_gpio)) + gpio_direction_output(panel->enable_gpio, + panel->enable_active_high); + + if (panel->enable_delay) + mdelay(panel->enable_delay); + + if (panel->backlight) { + ret = backlight_set_brightness_default(panel->backlight); + if (ret) + return ret; + } + + return 0; +} + +static int simple_panel_disable(struct simple_panel *panel) +{ + if (panel->backlight) + backlight_set_brightness(panel->backlight, 0); + + if (gpio_is_valid(panel->enable_gpio)) + gpio_direction_output(panel->enable_gpio, + !panel->enable_active_high); + + return 0; +} + +static int simple_panel_get_modes(struct simple_panel *panel, struct display_timings *timings) +{ + int ret = -ENOENT; + + if (panel->ddc_node) { + struct i2c_adapter *i2c; + + i2c = of_find_i2c_adapter_by_node(panel->ddc_node); + if (!i2c) + return -ENODEV; + timings->edid = edid_read_i2c(i2c); + if (!timings->edid) + return -EINVAL; + + ret = edid_to_display_timings(timings, timings->edid); + } + + return ret; +} + +static int simple_panel_ioctl(struct vpl *vpl, unsigned int port, + unsigned int cmd, void *ptr) +{ + struct simple_panel *panel = container_of(vpl, + struct simple_panel, vpl); + + switch (cmd) { + case VPL_ENABLE: + return simple_panel_enable(panel); + case VPL_DISABLE: + return simple_panel_disable(panel); + case VPL_GET_VIDEOMODES: + return simple_panel_get_modes(panel, ptr); + default: + return -ENOSYS; + } +} + +static int simple_panel_probe(struct device_d *dev) +{ + struct simple_panel *panel; + struct device_node *node = dev->device_node; + enum of_gpio_flags flags; + int ret; + + panel = xzalloc(sizeof(*panel)); + + panel->enable_gpio = of_get_named_gpio_flags(node, "enable-gpios", 0, &flags); + panel->vpl.node = node; + panel->vpl.ioctl = simple_panel_ioctl; + + if (gpio_is_valid(panel->enable_gpio)) { + if (!(flags & OF_GPIO_ACTIVE_LOW)) + panel->enable_active_high = 1; + } + + panel->ddc_node = of_parse_phandle(node, "ddc-i2c-bus", 0); + + of_property_read_u32(node, "enable-delay", &panel->enable_delay); + + panel->backlight_node = of_parse_phandle(node, "backlight", 0); + + ret = vpl_register(&panel->vpl); + if (ret) + return ret; + + return 0; +} + +static struct of_device_id simple_panel_of_ids[] = { + { .compatible = "simple-panel", }, + { } +}; + +static struct driver_d simple_panel_driver = { + .name = "simple-panel", + .probe = simple_panel_probe, + .of_compatible = DRV_OF_COMPAT(simple_panel_of_ids), +}; +device_platform_driver(simple_panel_driver); diff --git a/drivers/video/vpl.c b/drivers/video/vpl.c new file mode 100644 index 0000000000..99ad180eec --- /dev/null +++ b/drivers/video/vpl.c @@ -0,0 +1,115 @@ +/* + * Video pipeline (VPL) support for barebox + * + * (C) Copyright 2014 Sascha Hauer, Pengutronix + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#define pr_fmt(fmt) "VPL: " fmt + +#include <common.h> +#include <driver.h> +#include <of_graph.h> +#include <linux/list.h> +#include <video/vpl.h> + +static LIST_HEAD(vpls); + +int vpl_register(struct vpl *vpl) +{ + list_add_tail(&vpl->list, &vpls); + + pr_debug("%s: %s\n", __func__, vpl->node->full_name); + + return 0; +} + +struct vpl *of_find_vpl(struct device_node *node) +{ + struct vpl *vpl; + + list_for_each_entry(vpl, &vpls, list) + if (vpl->node == node) + return vpl; + + return NULL; +} + +struct vpl *of_vpl_get(struct device_node *node, int port) +{ + node = of_graph_get_port_by_id(node, port); + if (!node) + return NULL; + + pr_debug("%s: port: %s\n", __func__, node->full_name); + + node = of_graph_get_remote_port_parent(node); + if (!node) + return NULL; + + pr_debug("%s: remote port parent: %s\n", __func__, node->full_name); + + return of_find_vpl(node); +} + +int vpl_ioctl(struct vpl *vpl, unsigned int port, + unsigned int cmd, void *ptr) +{ + struct device_node *node, *endpoint; + int ret; + + pr_debug("%s: %s port %d\n", __func__, vpl->node->full_name, port); + + node = of_graph_get_port_by_id(vpl->node, port); + if (!node) { + pr_err("%s: no port %d on %s\n", __func__, port, vpl->node->full_name); + return -ENODEV; + } + + for_each_child_of_node(node, endpoint) { + struct device_node *remote, *remote_parent; + struct vpl *remote_vpl; + u32 remote_port_id; + + remote = of_graph_get_remote_port(endpoint); + if (!remote) { + pr_debug("%s: no remote for endpoint %s\n", __func__, endpoint->full_name); + continue; + } + + of_property_read_u32(remote, "reg", &remote_port_id); + + remote_parent = of_graph_get_remote_port_parent(endpoint); + if (!remote_parent) { + pr_debug("%s: no remote_parent\n", __func__); + return -ENODEV; + } + + if (!of_device_is_available(remote_parent)) + continue; + + remote_vpl = of_find_vpl(remote_parent); + if (!remote_vpl) { + pr_debug("%s: cannot find remote vpl %s\n", __func__, remote->full_name); + continue; + } + + ret = remote_vpl->ioctl(remote_vpl, remote_port_id, cmd, ptr); + if (ret) + return ret; + } + + return 0; +} diff --git a/include/fb.h b/include/fb.h index d0bab48bdd..311d5db192 100644 --- a/include/fb.h +++ b/include/fb.h @@ -99,6 +99,7 @@ struct display_timings { unsigned int num_modes; struct fb_videomode *modes; + void *edid; }; struct i2c_adapter; @@ -143,6 +144,7 @@ struct fb_info { }; struct display_timings *of_get_display_timings(struct device_node *np); +void display_timings_release(struct display_timings *); int register_framebuffer(struct fb_info *info); diff --git a/include/of.h b/include/of.h index 1db210b38a..e0ebc39b74 100644 --- a/include/of.h +++ b/include/of.h @@ -153,6 +153,8 @@ extern int of_device_is_available(const struct device_node *device); extern struct device_node *of_get_parent(const struct device_node *node); extern struct device_node *of_get_next_available_child( const struct device_node *node, struct device_node *prev); +struct device_node *of_get_next_child(const struct device_node *node, + struct device_node *prev); extern int of_get_child_count(const struct device_node *parent); extern int of_get_available_child_count(const struct device_node *parent); extern struct device_node *of_get_child_by_name(const struct device_node *node, @@ -308,6 +310,12 @@ static inline struct device_node *of_get_next_available_child( return NULL; } +static inline struct device_node *of_get_next_child(const struct device_node *node, + struct device_node *prev) +{ + return NULL; +} + static inline int of_get_child_count(const struct device_node *parent) { return -ENOSYS; diff --git a/include/of_graph.h b/include/of_graph.h new file mode 100644 index 0000000000..254dd2ca28 --- /dev/null +++ b/include/of_graph.h @@ -0,0 +1,50 @@ +/* + * OF graph binding parsing helpers + * + * Copyright (C) 2012 - 2013 Samsung Electronics Co., Ltd. + * Author: Sylwester Nawrocki <s.nawrocki@samsung.com> + * + * Copyright (C) 2012 Renesas Electronics Corp. + * Author: Guennadi Liakhovetski <g.liakhovetski@gmx.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + */ +#ifndef __LINUX_OF_GRAPH_H +#define __LINUX_OF_GRAPH_H + +/** + * struct of_endpoint - the OF graph endpoint data structure + * @port: identifier (value of reg property) of a port this endpoint belongs to + * @id: identifier (value of reg property) of this endpoint + * @local_node: pointer to device_node of this endpoint + */ +struct of_endpoint { + unsigned int port; + unsigned int id; + const struct device_node *local_node; +}; + +/** + * for_each_endpoint_of_node - iterate over every endpoint in a device node + * @parent: parent device node containing ports and endpoints + * @child: loop variable pointing to the current endpoint node + * + * When breaking out of the loop, of_node_put(child) has to be called manually. + */ +#define for_each_endpoint_of_node(parent, child) \ + for (child = of_graph_get_next_endpoint(parent, NULL); child != NULL; \ + child = of_graph_get_next_endpoint(parent, child)) + +int of_graph_parse_endpoint(const struct device_node *node, + struct of_endpoint *endpoint); +struct device_node *of_graph_get_port_by_id(struct device_node *node, u32 id); +struct device_node *of_graph_get_next_endpoint(const struct device_node *parent, + struct device_node *previous); +struct device_node *of_graph_get_remote_port_parent( + const struct device_node *node); +struct device_node *of_graph_get_remote_port(const struct device_node *node); +int of_graph_port_is_available(struct device_node *node); + +#endif /* __LINUX_OF_GRAPH_H */ diff --git a/include/video/vpl.h b/include/video/vpl.h new file mode 100644 index 0000000000..846007f4f5 --- /dev/null +++ b/include/video/vpl.h @@ -0,0 +1,46 @@ +#ifndef __VIDEO_VPL_H +#define __VIDEO_VPL_H + +#include <fb.h> + +#define VPL_PREPARE 0x67660001 +#define VPL_UNPREPARE 0x67660002 +#define VPL_ENABLE 0x67660003 +#define VPL_DISABLE 0x67660004 +#define VPL_GET_VIDEOMODES 0x67660005 + +struct vpl { + int (*ioctl)(struct vpl *, unsigned int port, + unsigned int cmd, void *ptr); + struct device_node *node; + struct list_head list; +}; + +int vpl_register(struct vpl *); +int vpl_ioctl(struct vpl *, unsigned int port, + unsigned int cmd, void *ptr); + +struct vpl *of_vpl_get(struct device_node *node, int port); + +static inline int vpl_ioctl_prepare(struct vpl *vpl, unsigned int port, + struct fb_videomode *mode) +{ + return vpl_ioctl(vpl, port, VPL_PREPARE, mode); +} + +static inline int vpl_ioctl_unprepare(struct vpl *vpl, unsigned int port) +{ + return vpl_ioctl(vpl, port, VPL_UNPREPARE, NULL); +} + +static inline int vpl_ioctl_enable(struct vpl *vpl, unsigned int port) +{ + return vpl_ioctl(vpl, port, VPL_ENABLE, NULL); +} + +static inline int vpl_ioctl_disable(struct vpl *vpl, unsigned int port) +{ + return vpl_ioctl(vpl, port, VPL_DISABLE, NULL); +} + +#endif /* __VIDEO_VPL_H */ |