This set adds some missing devicetree nodes to the exynos5250-snow file as well as adds a drm_bridge driver for the ptn3460 DP-LVDS chip. This chip is used in the exynos5250-snow board.
Sean
Sean Paul (5): ARM: dts: Add fimd display-timings for exynos5250-snow ARM: dts: Add dp-controller node to exynos5250-snow drm/bridge: Add PTN3460 bridge driver drm/exynos: Initialize ptn3460 if present ARM: dts: Add ptn3460 to exynos5250-snow
Documentation/devicetree/bindings/drm/bridge/ptn3460.txt | 27 +++++++++++++++++++++++++ arch/arm/boot/dts/exynos5250-snow.dts | 48 ++++++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/Kconfig | 2 ++ drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/bridge/Kconfig | 4 ++++ drivers/gpu/drm/bridge/Makefile | 3 +++ drivers/gpu/drm/bridge/ptn3460.c | 349 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/exynos/exynos_drm_core.c | 44 +++++++++++++++++++++++++++++++++++++++- include/drm/bridge/ptn3460.h | 36 +++++++++++++++++++++++++++++++++ 9 files changed, 513 insertions(+), 1 deletion(-)
This patch adds the internal panel timings to the exynos5250-snow board dts file.
Signed-off-by: Sean Paul seanpaul@chromium.org --- arch/arm/boot/dts/exynos5250-snow.dts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+)
diff --git a/arch/arm/boot/dts/exynos5250-snow.dts b/arch/arm/boot/dts/exynos5250-snow.dts index e79331d..e5af3f2 100644 --- a/arch/arm/boot/dts/exynos5250-snow.dts +++ b/arch/arm/boot/dts/exynos5250-snow.dts @@ -190,6 +190,23 @@ samsung,vbus-gpio = <&gpx1 1 0>; };
+ fimd: fimd@14400000 { + display-timings { + native-mode = <&lcd_timing>; + lcd_timing: 1366x768 { + clock-frequency = <70589280>; + hactive = <1366>; + vactive = <768>; + hfront-porch = <40>; + hback-porch = <40>; + hsync-len = <32>; + vback-porch = <10>; + vfront-porch = <12>; + vsync-len = <6>; + }; + }; + }; + fixed-rate-clocks { xxti { compatible = "samsung,clock-xxti";
This patch adds the dp-controller node to the exynos5250-snow board dts file.
Signed-off-by: Sean Paul seanpaul@chromium.org --- arch/arm/boot/dts/exynos5250-snow.dts | 12 ++++++++++++ 1 file changed, 12 insertions(+)
diff --git a/arch/arm/boot/dts/exynos5250-snow.dts b/arch/arm/boot/dts/exynos5250-snow.dts index e5af3f2..780511a 100644 --- a/arch/arm/boot/dts/exynos5250-snow.dts +++ b/arch/arm/boot/dts/exynos5250-snow.dts @@ -190,6 +190,18 @@ samsung,vbus-gpio = <&gpx1 1 0>; };
+ dp-controller { + samsung,color-space = <0>; + samsung,dynamic-range = <0>; + samsung,ycbcr-coeff = <0>; + samsung,color-depth = <1>; + samsung,link-rate = <0x0a>; + samsung,lane-count = <2>; + + pinctrl-names = "default"; + pinctrl-0 = <&dp_hpd>; + }; + fimd: fimd@14400000 { display-timings { native-mode = <&lcd_timing>;
On Tue, Oct 1, 2013 at 4:40 PM, Sean Paul seanpaul@chromium.org wrote:
This won't actually do what I think you want. Since the dtsi has the node name "dp-controller@145B0000", you will end up duplicating a new node and not build on top of that one.
There's two ways to do it: Always include the unit address, or create a label in the dtsi such that:
dp_controller: dp-controller@ {.... }
Then in your dts you can, at the root level:
&dp_controller { ... new stuff and overrides ... };
-Olof
On Wed, Oct 2, 2013 at 5:10 PM, Olof Johansson olof@lixom.net wrote:
Thanks, Olof. I based this off Inki's exynos-drm-next tree which does not have the latest from arm-soc, I'll re-upload.
Sean
This patch adds a drm_bridge driver for the PTN3460 DisplayPort to LVDS bridge chip.
Signed-off-by: Sean Paul seanpaul@chromium.org --- .../devicetree/bindings/drm/bridge/ptn3460.txt | 27 ++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/bridge/Kconfig | 4 + drivers/gpu/drm/bridge/Makefile | 3 + drivers/gpu/drm/bridge/ptn3460.c | 349 +++++++++++++++++++++ include/drm/bridge/ptn3460.h | 36 +++ 7 files changed, 422 insertions(+) create mode 100644 Documentation/devicetree/bindings/drm/bridge/ptn3460.txt create mode 100644 drivers/gpu/drm/bridge/Kconfig create mode 100644 drivers/gpu/drm/bridge/Makefile create mode 100644 drivers/gpu/drm/bridge/ptn3460.c create mode 100644 include/drm/bridge/ptn3460.h
diff --git a/Documentation/devicetree/bindings/drm/bridge/ptn3460.txt b/Documentation/devicetree/bindings/drm/bridge/ptn3460.txt new file mode 100644 index 0000000..c1cd329 --- /dev/null +++ b/Documentation/devicetree/bindings/drm/bridge/ptn3460.txt @@ -0,0 +1,27 @@ +ptn3460-bridge bindings + +Required properties: + - compatible: "nxp,ptn3460" + - reg: i2c address of the bridge + - powerdown-gpio: OF device-tree gpio specification + - reset-gpio: OF device-tree gpio specification + - edid-emulation: The EDID emulation entry to use + +-------+------------+------------------+ + | Value | Resolution | Description | + | 0 | 1024x768 | NXP Generic | + | 1 | 1920x1080 | NXP Generic | + | 2 | 1920x1080 | NXP Generic | + | 3 | 1600x900 | Samsung LTM200KT | + | 4 | 1920x1080 | Samsung LTM230HT | + | 5 | 1366x768 | NXP Generic | + | 6 | 1600x900 | ChiMei M215HGE | + +-------+------------+------------------+ + +Example: + ptn3460-bridge@20 { + compatible = "nxp,ptn3460"; + reg = <0x20>; + powerdown-gpio = <&gpy2 5 1 0 0>; + reset-gpio = <&gpx1 5 1 0 0>; + edid-emulation = <5>; + }; diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 955555d..cd7bfb3 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -236,3 +236,5 @@ source "drivers/gpu/drm/tilcdc/Kconfig" source "drivers/gpu/drm/qxl/Kconfig"
source "drivers/gpu/drm/msm/Kconfig" + +source "drivers/gpu/drm/bridge/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index f089adf..9234253 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -56,3 +56,4 @@ obj-$(CONFIG_DRM_TILCDC) += tilcdc/ obj-$(CONFIG_DRM_QXL) += qxl/ obj-$(CONFIG_DRM_MSM) += msm/ obj-y += i2c/ +obj-y += bridge/ diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig new file mode 100644 index 0000000..f8db069 --- /dev/null +++ b/drivers/gpu/drm/bridge/Kconfig @@ -0,0 +1,4 @@ +config DRM_PTN3460 + tristate "PTN3460 DP/LVDS bridge" + depends on DRM && I2C + ---help--- diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile new file mode 100644 index 0000000..b4733e1 --- /dev/null +++ b/drivers/gpu/drm/bridge/Makefile @@ -0,0 +1,3 @@ +ccflags-y := -Iinclude/drm + +obj-$(CONFIG_DRM_PTN3460) += ptn3460.o diff --git a/drivers/gpu/drm/bridge/ptn3460.c b/drivers/gpu/drm/bridge/ptn3460.c new file mode 100644 index 0000000..a9e5c1a --- /dev/null +++ b/drivers/gpu/drm/bridge/ptn3460.c @@ -0,0 +1,349 @@ +/* + * NXP PTN3460 DP/LVDS bridge driver + * + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 <linux/module.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/delay.h> + +#include "drmP.h" +#include "drm_edid.h" +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + +#include "bridge/ptn3460.h" + +#define PTN3460_EDID_ADDR 0x0 +#define PTN3460_EDID_EMULATION_ADDR 0x84 +#define PTN3460_EDID_ENABLE_EMULATION 0 +#define PTN3460_EDID_EMULATION_SELECTION 1 +#define PTN3460_EDID_SRAM_LOAD_ADDR 0x85 + +struct ptn3460_bridge { + struct drm_connector connector; + struct i2c_client *client; + struct drm_encoder *encoder; + struct drm_bridge *bridge; + struct edid *edid; + int gpio_pd_n; + int gpio_rst_n; + u32 edid_emulation; + bool enabled; +}; + +static int ptn3460_read_bytes(struct ptn3460_bridge *ptn_bridge, char addr, + u8 *buf, int len) +{ + int ret; + + ret = i2c_master_send(ptn_bridge->client, &addr, 1); + if (ret <= 0) { + DRM_ERROR("Failed to send i2c command, ret=%d\n", ret); + return ret; + } + + ret = i2c_master_recv(ptn_bridge->client, buf, len); + if (ret <= 0) { + DRM_ERROR("Failed to recv i2c data, ret=%d\n", ret); + return ret; + } + + return 0; +} + +static int ptn3460_write_byte(struct ptn3460_bridge *ptn_bridge, char addr, + char val) +{ + int ret; + char buf[2]; + + buf[0] = addr; + buf[1] = val; + + ret = i2c_master_send(ptn_bridge->client, buf, ARRAY_SIZE(buf)); + if (ret <= 0) { + DRM_ERROR("Failed to send i2c command, ret=%d\n", ret); + return ret; + } + + return 0; +} + +static int ptn3460_select_edid(struct ptn3460_bridge *ptn_bridge) +{ + int ret; + char val; + + /* Load the selected edid into SRAM (accessed at PTN3460_EDID_ADDR) */ + ret = ptn3460_write_byte(ptn_bridge, PTN3460_EDID_SRAM_LOAD_ADDR, + ptn_bridge->edid_emulation); + if (ret) { + DRM_ERROR("Failed to transfer edid to sram, ret=%d\n", ret); + return ret; + } + + /* Enable EDID emulation and select the desired EDID */ + val = 1 << PTN3460_EDID_ENABLE_EMULATION | + ptn_bridge->edid_emulation << PTN3460_EDID_EMULATION_SELECTION; + + ret = ptn3460_write_byte(ptn_bridge, PTN3460_EDID_EMULATION_ADDR, val); + if (ret) { + DRM_ERROR("Failed to write edid value, ret=%d\n", ret); + return ret; + } + + return 0; +} + +static void ptn3460_pre_enable(struct drm_bridge *bridge) +{ + struct ptn3460_bridge *ptn_bridge = bridge->driver_private; + int ret; + + if (ptn_bridge->enabled) + return; + + if (gpio_is_valid(ptn_bridge->gpio_pd_n)) + gpio_set_value(ptn_bridge->gpio_pd_n, 1); + + if (gpio_is_valid(ptn_bridge->gpio_rst_n)) { + gpio_set_value(ptn_bridge->gpio_rst_n, 0); + udelay(10); + gpio_set_value(ptn_bridge->gpio_rst_n, 1); + } + + /* + * There's a bug in the PTN chip where it falsely asserts hotplug before + * it is fully functional. We're forced to wait for the maximum start up + * time specified in the chip's datasheet to make sure we're really up. + */ + msleep(90); + + ret = ptn3460_select_edid(ptn_bridge); + if (ret) + DRM_ERROR("Select edid failed ret=%d\n", ret); + + ptn_bridge->enabled = true; +} + +static void ptn3460_enable(struct drm_bridge *bridge) +{ +} + +static void ptn3460_disable(struct drm_bridge *bridge) +{ + struct ptn3460_bridge *ptn_bridge = bridge->driver_private; + + if (!ptn_bridge->enabled) + return; + + ptn_bridge->enabled = false; + + if (gpio_is_valid(ptn_bridge->gpio_rst_n)) + gpio_set_value(ptn_bridge->gpio_rst_n, 1); + + if (gpio_is_valid(ptn_bridge->gpio_pd_n)) + gpio_set_value(ptn_bridge->gpio_pd_n, 0); +} + +static void ptn3460_post_disable(struct drm_bridge *bridge) +{ +} + +void ptn3460_bridge_destroy(struct drm_bridge *bridge) +{ + struct ptn3460_bridge *ptn_bridge = bridge->driver_private; + + drm_bridge_cleanup(bridge); + if (gpio_is_valid(ptn_bridge->gpio_pd_n)) + gpio_free(ptn_bridge->gpio_pd_n); + if (gpio_is_valid(ptn_bridge->gpio_rst_n)) + gpio_free(ptn_bridge->gpio_rst_n); + /* Nothing else to free, we've got devm allocated memory */ +} + +struct drm_bridge_funcs ptn3460_bridge_funcs = { + .pre_enable = ptn3460_pre_enable, + .enable = ptn3460_enable, + .disable = ptn3460_disable, + .post_disable = ptn3460_post_disable, + .destroy = ptn3460_bridge_destroy, +}; + +int ptn3460_get_modes(struct drm_connector *connector) +{ + struct ptn3460_bridge *ptn_bridge; + u8 *edid; + int ret, num_modes; + bool power_off; + + ptn_bridge = container_of(connector, struct ptn3460_bridge, connector); + + if (ptn_bridge->edid) + return drm_add_edid_modes(connector, ptn_bridge->edid); + + power_off = !ptn_bridge->enabled; + ptn3460_pre_enable(ptn_bridge->bridge); + + edid = kmalloc(EDID_LENGTH, GFP_KERNEL); + if (!edid) { + DRM_ERROR("Failed to allocate edid\n"); + return 0; + } + + ret = ptn3460_read_bytes(ptn_bridge, PTN3460_EDID_ADDR, edid, + EDID_LENGTH); + if (ret) { + kfree(edid); + num_modes = 0; + goto out; + } + + ptn_bridge->edid = (struct edid *)edid; + drm_mode_connector_update_edid_property(connector, ptn_bridge->edid); + + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); + +out: + if (power_off) + ptn3460_disable(ptn_bridge->bridge); + + return num_modes; +} + +static int ptn3460_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +struct drm_encoder *ptn3460_best_encoder(struct drm_connector *connector) +{ + struct ptn3460_bridge *ptn_bridge; + + ptn_bridge = container_of(connector, struct ptn3460_bridge, connector); + + return ptn_bridge->encoder; +} + +struct drm_connector_helper_funcs ptn3460_connector_helper_funcs = { + .get_modes = ptn3460_get_modes, + .mode_valid = ptn3460_mode_valid, + .best_encoder = ptn3460_best_encoder, +}; + +enum drm_connector_status ptn3460_detect(struct drm_connector *connector, + bool force) +{ + return connector_status_connected; +} + +void ptn3460_connector_destroy(struct drm_connector *connector) +{ + drm_connector_cleanup(connector); +} + +struct drm_connector_funcs ptn3460_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = ptn3460_detect, + .destroy = ptn3460_connector_destroy, +}; + +int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder, + struct i2c_client *client, struct device_node *node) +{ + int ret; + struct drm_bridge *bridge; + struct ptn3460_bridge *ptn_bridge; + + bridge = devm_kzalloc(dev->dev, sizeof(*bridge), GFP_KERNEL); + if (!bridge) { + DRM_ERROR("Failed to allocate drm bridge\n"); + return -ENOMEM; + } + + ptn_bridge = devm_kzalloc(dev->dev, sizeof(*ptn_bridge), GFP_KERNEL); + if (!ptn_bridge) { + DRM_ERROR("Failed to allocate ptn bridge\n"); + return -ENOMEM; + } + + ptn_bridge->client = client; + ptn_bridge->encoder = encoder; + ptn_bridge->bridge = bridge; + ptn_bridge->gpio_pd_n = of_get_named_gpio(node, "powerdown-gpio", 0); + if (gpio_is_valid(ptn_bridge->gpio_pd_n)) { + ret = gpio_request_one(ptn_bridge->gpio_pd_n, + GPIOF_OUT_INIT_HIGH, "PTN3460_PD_N"); + if (ret) { + DRM_ERROR("Request powerdown-gpio failed (%d)\n", ret); + return ret; + } + } + + ptn_bridge->gpio_rst_n = of_get_named_gpio(node, "reset-gpio", 0); + if (gpio_is_valid(ptn_bridge->gpio_rst_n)) { + /* + * Request the reset pin low to avoid the bridge being + * initialized prematurely + */ + ret = gpio_request_one(ptn_bridge->gpio_rst_n, + GPIOF_OUT_INIT_LOW, "PTN3460_RST_N"); + if (ret) { + DRM_ERROR("Request reset-gpio failed (%d)\n", ret); + gpio_free(ptn_bridge->gpio_pd_n); + return ret; + } + } + + ret = of_property_read_u32(node, "edid-emulation", + &ptn_bridge->edid_emulation); + if (ret) { + DRM_ERROR("Can't read edid emulation value\n"); + goto err; + } + + ret = drm_bridge_init(dev, bridge, &ptn3460_bridge_funcs); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + goto err; + } + + bridge->driver_private = ptn_bridge; + encoder->bridge = bridge; + + ret = drm_connector_init(dev, &ptn_bridge->connector, + &ptn3460_connector_funcs, DRM_MODE_CONNECTOR_LVDS); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + goto err; + } + drm_connector_helper_add(&ptn_bridge->connector, + &ptn3460_connector_helper_funcs); + drm_sysfs_connector_add(&ptn_bridge->connector); + drm_mode_connector_attach_encoder(&ptn_bridge->connector, encoder); + + return 0; + +err: + if (gpio_is_valid(ptn_bridge->gpio_pd_n)) + gpio_free(ptn_bridge->gpio_pd_n); + if (gpio_is_valid(ptn_bridge->gpio_rst_n)) + gpio_free(ptn_bridge->gpio_rst_n); + return ret; +} diff --git a/include/drm/bridge/ptn3460.h b/include/drm/bridge/ptn3460.h new file mode 100644 index 0000000..157ffa1 --- /dev/null +++ b/include/drm/bridge/ptn3460.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef _DRM_BRIDGE_PTN3460_H_ +#define _DRM_BRIDGE_PTN3460_H_ + +struct drm_device; +struct drm_encoder; +struct i2c_client; +struct device_node; + +#ifdef CONFIG_DRM_PTN3460 + +int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder, + struct i2c_client *client, struct device_node *node); +#else + +int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder, + struct i2c_client *client, struct device_node *node) +{ + return 0; +} + +#endif + +#endif
Hi,
On Tue, Oct 1, 2013 at 4:40 PM, Sean Paul seanpaul@chromium.org wrote:
[...]
Nit: Name is usually generic device name, i.e. "lvds-bridge" or something like that.
[...]
This should be static inline int ptn3460_init(...)
-0Olof
Hi, thank you for your contribution and the below is my short comments,
2013/10/2 Sean Paul seanpaul@chromium.org:
Can a regulator be used instead of gpio in other board case?
Ditto.
Ditto.
Ditto.
Why do you try to add a new connector here? We already have the connector for LCD, and also provides some callbacks for it. For this, please see exynos_drm_display_ops of exynos_drm_fimd driver, and you can add new callbacks to there such as init callback for bridge device initialization if needed.
Also, if a regulator is used instead?
So it seems that here's not right place to call drm_connector_init function.
Display pipeline path could be one of, Display Controller Display bus --------------------------------------------------------------------------- FIMD---------------------LVDS--------------------LCD, or FIMD----------------------eDP---------------------LCD, or FIMD------------------MIPI-DSI------------------LCD, or FIMD-------------------------------------------------LCD
And also in case using image enhancement chip, mDNIe-------------FIMD-LITE between Display Controller and Display bus.
So, wouldn't the right place below FIMD driver? :)
On Thu, Oct 3, 2013 at 9:55 AM, Inki Dae inki.dae@samsung.com wrote:
No, not to my knowledge.
We add a new connector for 2 reasons:
1) We need to override the drm detect() callback to always return true since the DP driver will presumably return its hotplug status which will always be low when the ptn chip is turned off. 2) We want the ability to control the result of get_modes().
I've got a patch set almost ready to tear the display ops out of fimd and put them in the DP driver. The display ops are better suited there, since it's the actual encoder/connector. I hope to get that posted this week.
Well, this driver should be considered outside of exynos context since it could be used by any drm driver.
In the exynos context, the right place to implement it would be in the dp driver, actually. However, the exynos driver has a level of abstraction on top of the crtcs/encoders such that we need to initialize it in the exynos_drm_core. The patchset I mentioned above should help move things in a direction where fimd/mixer implement drm_crtc directly and hdmi/dp implement drm_encoder/drm_connector directly. In that world, DP would initialize the ptn driver.
2013/10/3 Sean Paul seanpaul@chromium.org:
Hm.. plz check it out again. the gpio pin is specific to board, and the the gpio be used as power source trigger could be replaced with a regulator according to board design. So you should consider all possibilities even though there are no other cases yet: other board could use a regulator instead.
Really? if so, that is ideal something we want and we should go. But isn't the DP driver placed in drivers/video/exynos? How did you take care of that? Actually, for this, we planned to use CDF(Common Display Framework) if the framework is merged to mainline somehow.
I will look forward to that posting. :)
On Thu, Oct 3, 2013 at 10:39 AM, Inki Dae inki.dae@samsung.com wrote:
Take a look at the data sheet, it is publicly available.
PD_N is not a power supply input, so modelling it as a regulator makes no sense:
"If PD_N is LOW, then the device is in Deep power-down completely, even if supply rail is ON; for the device to be able to operate, the PD_N pin must be HIGH."
-Olof
2013/10/4 Olof Johansson olof@lixom.net:
I still think the pin could be replaced with a regulator. But lvds-bridge node has "powerdown-gpio" property - it say this board will use gpio pin - specific to board. So it seems no problem.
On Fri, Oct 04, 2013 at 11:05:48AM +0900, Inki Dae wrote:
2013/10/4 Olof Johansson olof@lixom.net:
No, don't model things that aren't regulators as regulators - it's just confusing from a usability standpoint and causes breakage when the pins don't behave like regulators.
On Thu, Oct 10, 2013 at 01:18:05PM +0900, Inki Dae wrote:
It seems that there was your missing point. That _is not_ what I mentioned. I mean that other boards can use a regulator instead of gpio pin.
What I'm saying is no boards should use a regulator to control that GPIO pin, obviously if they're controlling the actual regulators that's fine but the reset signal should not be controlled via the regulator API (there are some unfortunate cases where people have done that already but let's not have any more).
On Thu, Oct 10, 2013 at 08:40:38PM +0900, Inki Dae wrote:
For the driver this should be totally transparent - it should just control the regulator all the time, the regulator API will just not do anything if the regulator state can't actually be changed.
On Thu, Oct 3, 2013 at 1:39 PM, Inki Dae inki.dae@samsung.com wrote:
git mv :)
Actually, for this, we planned to use CDF(Common Display Framework) if the framework is merged to mainline somehow.
Right. I think CDF will end up being a series of improvements to drm, as opposed to its own framework (at least this was the conclusion I came to after speaking with Laurent at the plumbers conference). I don't think it makes sense to have the DP driver outside of drm. The HDMI driver is already inside drm, the DP driver should be too. Regardless, this conversation is only tangentially related to this patch and can probably be deferred.
Sean
2013/10/4 Sean Paul seanpaul@chromium.org:
:)
See the below,
Application -------------------------------------------------------------- v4l2 drm kms hdmi driver hdmi driver ------------------------------------------------------------- hdmi hw
HDMI is a controller can be controlled by user application, and for this some frameworks such as v4l2 and drm kms interfaces are used. But DP, MIPI-DSI, LVDS, and so on aren't controlled by user application. These are just display bus between scanout devices(hdmi, fimd) and lcd panel. So the above your example doesn't make sense.
On Thu, Oct 3, 2013 at 2:23 PM, Inki Dae inki.dae@samsung.com wrote:
I think we've probably gone far enough off-topic. Let's discuss this when you've had an opportunity to see the patchset. I hope the code will speak for itself.
Aside from Olof's suggestion about changing the dts binding to lvds-bridge (which I'll upload a new patch for), do you have any suggestions for this patch?
Sean
2013/10/4 Sean Paul seanpaul@chromium.org:
Ok, let's have a discussion after looking into the codes.
lvds-bridge is better. It seems reasonable to me.
This patch adds code to look for the ptn3460 in the device tree file on exynos initialization. If ptn node is found, the driver will initialize the ptn3460 driver and skip creating a DP connector (since the bridge driver will register its own connector).
Signed-off-by: Sean Paul seanpaul@chromium.org --- drivers/gpu/drm/exynos/exynos_drm_core.c | 44 +++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_core.c b/drivers/gpu/drm/exynos/exynos_drm_core.c index 1bef6dc..9cf4476 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_core.c +++ b/drivers/gpu/drm/exynos/exynos_drm_core.c @@ -12,7 +12,9 @@ * option) any later version. */
+#include <linux/of_i2c.h> #include <drm/drmP.h> +#include <drm/bridge/ptn3460.h> #include "exynos_drm_drv.h" #include "exynos_drm_encoder.h" #include "exynos_drm_connector.h" @@ -20,6 +22,40 @@
static LIST_HEAD(exynos_drm_subdrv_list);
+struct bridge_init { + struct i2c_client *client; + struct device_node *node; +}; + +static bool find_bridge(const char *name, struct bridge_init *bridge) +{ + bridge->client = NULL; + bridge->node = of_find_node_by_name(NULL, name); + if (!bridge->node) + return false; + + bridge->client = of_find_i2c_device_by_node(bridge->node); + if (!bridge->client) + return false; + + return true; +} + +/* returns the number of bridges attached */ +static int exynos_drm_attach_lcd_bridge(struct drm_device *dev, + struct drm_encoder *encoder) +{ + struct bridge_init bridge; + int ret; + + if (find_bridge("ptn3460-bridge", &bridge)) { + ret = ptn3460_init(dev, encoder, bridge.client, bridge.node); + if (!ret) + return 1; + } + return 0; +} + static int exynos_drm_create_enc_conn(struct drm_device *dev, struct exynos_drm_subdrv *subdrv) { @@ -36,6 +72,13 @@ static int exynos_drm_create_enc_conn(struct drm_device *dev, DRM_ERROR("failed to create encoder\n"); return -EFAULT; } + subdrv->encoder = encoder; + + if (subdrv->manager->display_ops->type == EXYNOS_DISPLAY_TYPE_LCD) { + ret = exynos_drm_attach_lcd_bridge(dev, encoder); + if (ret) + return 0; + }
/* * create and initialize a connector for this sub driver and @@ -48,7 +91,6 @@ static int exynos_drm_create_enc_conn(struct drm_device *dev, goto err_destroy_encoder; }
- subdrv->encoder = encoder; subdrv->connector = connector;
return 0;
2013/10/2 Sean Paul seanpaul@chromium.org:
Not clear to me. Why do you try to handle device tree here, not real device driver?. How about adding a output property to board specific fimd dt node: i.e. output = <&ptn3460_bridge>? Actually, the output device of fimd hw could be one of MIPI-DSI, eDP, mDNIe, LVDS bridge, or LCD. And then, let's find ptn3460-bridge node in the fimd driver, and initialize the ptn3460 bridge driver, and get power on or off through exynos_drm_display_ops of the fimd driver. And all these codes could be hided from fimd driver by moving them into exynos_drm_display_ops. Of course, for this, you would need additional works. So let's do it if needed.
The below is the outline of device tree I recommend,
In board dts, i2c@I2CD000 { ptn3460_bridge: prn3460-bridge@20 { ... } }
fimd@11c00000 { ... output_dev = <&ptn3460_bridge>; }
With this, I believe that you can do all things you want for controlling the LVDS bridge in fimd driver.
Thanks, Inki Dae
On Thu, Oct 3, 2013 at 10:43 AM, Inki Dae inki.dae@samsung.com wrote:
The problem doing something like this is that we won't have a handle to drm_device if it's just a standalone driver, and so we won't be able to register the bridge or connector. We need this init call one way or another.
No, this isn't what I want to do. The bridge should not hang off fimd since it's a crtc. The bridge should only be initialized by the DP driver (it doesn't make sense to initialize ptn when you're using MIPI/LVDS/whatever).
Since the actual crtc/encoder drivers are abstracted through exynos_drm_core/crtc/encoder, we need to initialize the ptn driver in the abstraction layers in order to hook it directly into drm.
Sean
2013/10/4 Sean Paul seanpaul@chromium.org:
At least, dt binding shoul be done in real device driver so this way is not good. Let's find a better way.
I don't mean that the bridge device should be initialized by fimd directly but the fimd driver provides just interfaces abstracted to control the bridge device. And basically, the exynos_drm_display_ops shouldn't be in fimd driver but in real connector driver; i.e. lcd panel or LVDS driver. The reason I placed the exynos_drm_display_ops in fimd driver is that lcd panel driver is controlled by lcd class depended on Linux framebuffer, and I thought the panel driver should be shared with drm driver in case of ARM SoC. The exynos_drm_display_ops should be moved into right place if something better exists some time or other.
So how can the DP driver control the bridge device as of now? the DP you mentioned would be eDP, and the eDP driver is placed in drivers/video/exynos/, and also MIPI-DSI driver.
On Thu, Oct 3, 2013 at 1:18 PM, Inki Dae inki.dae@samsung.com wrote:
Right, so this is kind of tricky. If you do it in a "real" device driver, you end up parsing the dt stuff in the probe, and then racing the init callback. I figured it would be best just to do everything in one place without races.
Hopefully I'm just missing a good way to solve this problem, any concrete ideas?
It can't. The DP driver just operates on its own and display either comes up or it doesn't depending on whether fimd happens to be initialized first. As I mentioned earlier, I have a patch set which moves DP driver into drm/exynos and removes the display_ops from fimd. That will fix this issue.
Sean
2013/10/4 Sean Paul seanpaul@chromium.org:
Ok, I don't know the DP hardware well. But, MIPI-DSI driver is depending on fimd on/off ordering. ie. to enable display hw pipe, the ordering should be FIMD----MIPI-DSI-----LCD because initial commands _cannot be set_ to lcd panel if fimd is off. And to disable that, the ordering should be LCD-------MIPI-DSI-------FIMD in same reason: to get lcd panel off, off commands should be set to lcd panel. In similar reason, I had posted FB_EARLY_EVENT_BLANK feature to mainline and that have been merged.
Ah... as you may know I mentioned about this issue some time ago; moving eDP driver(maybe??) into drm/exynos. At that time, I had rejected it because I thought we should share these bus drivers with Linux framebuffer driver: actually, I planned to use CDF for this. But.... I'm not sure even CDF as of now. So let's have a discussion about this issue with other people: it doesn't mean I oppose your patch set.
This patch adds a node for the ptn3460 DP-LVDS chip in the exynos5250-snow board dts file.
Signed-off-by: Sean Paul seanpaul@chromium.org --- arch/arm/boot/dts/exynos5250-snow.dts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+)
diff --git a/arch/arm/boot/dts/exynos5250-snow.dts b/arch/arm/boot/dts/exynos5250-snow.dts index 780511a..122fc1f 100644 --- a/arch/arm/boot/dts/exynos5250-snow.dts +++ b/arch/arm/boot/dts/exynos5250-snow.dts @@ -33,6 +33,13 @@ sd3_bus4: sd3-bus-width4 { samsung,pin-drv = <0>; }; + + ptn3460_gpios: ptn3460-gpios { + samsung,pins = "gpy2-5", "gpx1-5"; + samsung,pin-function = <1>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; };
gpio-keys { @@ -190,6 +197,18 @@ samsung,vbus-gpio = <&gpx1 1 0>; };
+ i2c@12CD0000 { + ptn3460-bridge@20 { + compatible = "nxp,ptn3460"; + reg = <0x20>; + powerdown-gpio = <&gpy2 5 0>; + reset-gpio = <&gpx1 5 0>; + edid-emulation = <5>; + pinctrl-names = "default"; + pinctrl-0 = <&ptn3460_gpios>; + }; + }; + dp-controller { samsung,color-space = <0>; samsung,dynamic-range = <0>;
Hi,
On Tue, Oct 1, 2013 at 4:40 PM, Sean Paul seanpaul@chromium.org wrote:
This should go in through Kukjin and the arm-soc tree, not through DRM (Looking at the actual patch shortly).
Tip: When you generate the diffstat, redirect to a file or size your window to something less than, oh, 300 characters wide or so. Maybe even down to 80. :)
-Olof
This set adds some missing devicetree nodes to the exynos5250-snow file as well as adds a drm_bridge driver for the ptn3460 DP-LVDS chip. This chip is used in the exynos5250-snow board.
Sean
Sean Paul (5): ARM: dts: Add fimd display-timings for exynos5250-snow ARM: dts: Add dp-controller node to exynos5250-snow drm/bridge: Add PTN3460 bridge driver drm/exynos: Initialize ptn3460 if present ARM: dts: Add ptn3460 to exynos5250-snow
Documentation/devicetree/bindings/drm/bridge/ptn3460.txt | 27 +++ arch/arm/boot/dts/exynos5250-snow.dts | 48 +++++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/bridge/Kconfig | 4 + drivers/gpu/drm/bridge/Makefile | 3 + drivers/gpu/drm/bridge/ptn3460.c | 349 ++++++++++++++++ drivers/gpu/drm/exynos/exynos_drm_core.c | 44 ++++- include/drm/bridge/ptn3460.h | 37 ++++ 9 files changed, 514 insertions(+), 1 deletion(-)
This patch adds the internal panel timings to the exynos5250-snow board dts file.
Signed-off-by: Sean Paul seanpaul@chromium.org ---
v2: No difference
arch/arm/boot/dts/exynos5250-snow.dts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+)
diff --git a/arch/arm/boot/dts/exynos5250-snow.dts b/arch/arm/boot/dts/exynos5250-snow.dts index fd711e2..2a0f0de 100644 --- a/arch/arm/boot/dts/exynos5250-snow.dts +++ b/arch/arm/boot/dts/exynos5250-snow.dts @@ -186,6 +186,23 @@ samsung,vbus-gpio = <&gpx1 1 0>; };
+ fimd: fimd@14400000 { + display-timings { + native-mode = <&lcd_timing>; + lcd_timing: 1366x768 { + clock-frequency = <70589280>; + hactive = <1366>; + vactive = <768>; + hfront-porch = <40>; + hback-porch = <40>; + hsync-len = <32>; + vback-porch = <10>; + vfront-porch = <12>; + vsync-len = <6>; + }; + }; + }; + fixed-rate-clocks { xxti { compatible = "samsung,clock-xxti";
On Friday, October 04, 2013 7:28 AM, Sean Paul wrote:
How about adding 'status = "okay"' as below?
fimd: fimd@14400000 { status = "okay"; display-timings {
status of fimd node was disabled by exynos5.dtsi
./arch/arm/boot/dts/exynos5.dtsi fimd@14400000 { status = "disabled";
Best regards, Jingoo Han
This patch adds the dp-controller node to the exynos5250-snow board dts file.
Signed-off-by: Sean Paul seanpaul@chromium.org ---
v2: Added dp-controller address to node (rebased on linux-next)
arch/arm/boot/dts/exynos5250-snow.dts | 12 ++++++++++++ 1 file changed, 12 insertions(+)
diff --git a/arch/arm/boot/dts/exynos5250-snow.dts b/arch/arm/boot/dts/exynos5250-snow.dts index 2a0f0de..f1cf68e 100644 --- a/arch/arm/boot/dts/exynos5250-snow.dts +++ b/arch/arm/boot/dts/exynos5250-snow.dts @@ -186,6 +186,18 @@ samsung,vbus-gpio = <&gpx1 1 0>; };
+ dp-controller@145B0000 { + samsung,color-space = <0>; + samsung,dynamic-range = <0>; + samsung,ycbcr-coeff = <0>; + samsung,color-depth = <1>; + samsung,link-rate = <0x0a>; + samsung,lane-count = <2>; + + pinctrl-names = "default"; + pinctrl-0 = <&dp_hpd>; + }; + fimd: fimd@14400000 { display-timings { native-mode = <&lcd_timing>;
On Friday, October 04, 2013 7:28 AM, Sean Paul wrote:
How about adding 'status = "okay" as below?
pinctrl-names = "default"; pinctrl-0 = <&dp_hpd>; status = "okay";
status of dp node was disabled by exynos5.dtsi.
./arch/arm/boot/dts/exynos5.dtsi dp-controller@145B0000 { status = "disabled";
Best regards, Jingoo Han
This patch adds a drm_bridge driver for the PTN3460 DisplayPort to LVDS bridge chip.
Signed-off-by: Sean Paul seanpaul@chromium.org ---
v2: - Changed header definition to static inline - Changed dt node name to lvds-bridge
.../devicetree/bindings/drm/bridge/ptn3460.txt | 27 ++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/bridge/Kconfig | 4 + drivers/gpu/drm/bridge/Makefile | 3 + drivers/gpu/drm/bridge/ptn3460.c | 349 +++++++++++++++++++++ include/drm/bridge/ptn3460.h | 37 +++ 7 files changed, 423 insertions(+) create mode 100644 Documentation/devicetree/bindings/drm/bridge/ptn3460.txt create mode 100644 drivers/gpu/drm/bridge/Kconfig create mode 100644 drivers/gpu/drm/bridge/Makefile create mode 100644 drivers/gpu/drm/bridge/ptn3460.c create mode 100644 include/drm/bridge/ptn3460.h
diff --git a/Documentation/devicetree/bindings/drm/bridge/ptn3460.txt b/Documentation/devicetree/bindings/drm/bridge/ptn3460.txt new file mode 100644 index 0000000..52b93b2 --- /dev/null +++ b/Documentation/devicetree/bindings/drm/bridge/ptn3460.txt @@ -0,0 +1,27 @@ +ptn3460 bridge bindings + +Required properties: + - compatible: "nxp,ptn3460" + - reg: i2c address of the bridge + - powerdown-gpio: OF device-tree gpio specification + - reset-gpio: OF device-tree gpio specification + - edid-emulation: The EDID emulation entry to use + +-------+------------+------------------+ + | Value | Resolution | Description | + | 0 | 1024x768 | NXP Generic | + | 1 | 1920x1080 | NXP Generic | + | 2 | 1920x1080 | NXP Generic | + | 3 | 1600x900 | Samsung LTM200KT | + | 4 | 1920x1080 | Samsung LTM230HT | + | 5 | 1366x768 | NXP Generic | + | 6 | 1600x900 | ChiMei M215HGE | + +-------+------------+------------------+ + +Example: + lvds-bridge@20 { + compatible = "nxp,ptn3460"; + reg = <0x20>; + powerdown-gpio = <&gpy2 5 1 0 0>; + reset-gpio = <&gpx1 5 1 0 0>; + edid-emulation = <5>; + }; diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 955555d..cd7bfb3 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -236,3 +236,5 @@ source "drivers/gpu/drm/tilcdc/Kconfig" source "drivers/gpu/drm/qxl/Kconfig"
source "drivers/gpu/drm/msm/Kconfig" + +source "drivers/gpu/drm/bridge/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index f089adf..9234253 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -56,3 +56,4 @@ obj-$(CONFIG_DRM_TILCDC) += tilcdc/ obj-$(CONFIG_DRM_QXL) += qxl/ obj-$(CONFIG_DRM_MSM) += msm/ obj-y += i2c/ +obj-y += bridge/ diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig new file mode 100644 index 0000000..f8db069 --- /dev/null +++ b/drivers/gpu/drm/bridge/Kconfig @@ -0,0 +1,4 @@ +config DRM_PTN3460 + tristate "PTN3460 DP/LVDS bridge" + depends on DRM && I2C + ---help--- diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile new file mode 100644 index 0000000..b4733e1 --- /dev/null +++ b/drivers/gpu/drm/bridge/Makefile @@ -0,0 +1,3 @@ +ccflags-y := -Iinclude/drm + +obj-$(CONFIG_DRM_PTN3460) += ptn3460.o diff --git a/drivers/gpu/drm/bridge/ptn3460.c b/drivers/gpu/drm/bridge/ptn3460.c new file mode 100644 index 0000000..a9e5c1a --- /dev/null +++ b/drivers/gpu/drm/bridge/ptn3460.c @@ -0,0 +1,349 @@ +/* + * NXP PTN3460 DP/LVDS bridge driver + * + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 <linux/module.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/delay.h> + +#include "drmP.h" +#include "drm_edid.h" +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + +#include "bridge/ptn3460.h" + +#define PTN3460_EDID_ADDR 0x0 +#define PTN3460_EDID_EMULATION_ADDR 0x84 +#define PTN3460_EDID_ENABLE_EMULATION 0 +#define PTN3460_EDID_EMULATION_SELECTION 1 +#define PTN3460_EDID_SRAM_LOAD_ADDR 0x85 + +struct ptn3460_bridge { + struct drm_connector connector; + struct i2c_client *client; + struct drm_encoder *encoder; + struct drm_bridge *bridge; + struct edid *edid; + int gpio_pd_n; + int gpio_rst_n; + u32 edid_emulation; + bool enabled; +}; + +static int ptn3460_read_bytes(struct ptn3460_bridge *ptn_bridge, char addr, + u8 *buf, int len) +{ + int ret; + + ret = i2c_master_send(ptn_bridge->client, &addr, 1); + if (ret <= 0) { + DRM_ERROR("Failed to send i2c command, ret=%d\n", ret); + return ret; + } + + ret = i2c_master_recv(ptn_bridge->client, buf, len); + if (ret <= 0) { + DRM_ERROR("Failed to recv i2c data, ret=%d\n", ret); + return ret; + } + + return 0; +} + +static int ptn3460_write_byte(struct ptn3460_bridge *ptn_bridge, char addr, + char val) +{ + int ret; + char buf[2]; + + buf[0] = addr; + buf[1] = val; + + ret = i2c_master_send(ptn_bridge->client, buf, ARRAY_SIZE(buf)); + if (ret <= 0) { + DRM_ERROR("Failed to send i2c command, ret=%d\n", ret); + return ret; + } + + return 0; +} + +static int ptn3460_select_edid(struct ptn3460_bridge *ptn_bridge) +{ + int ret; + char val; + + /* Load the selected edid into SRAM (accessed at PTN3460_EDID_ADDR) */ + ret = ptn3460_write_byte(ptn_bridge, PTN3460_EDID_SRAM_LOAD_ADDR, + ptn_bridge->edid_emulation); + if (ret) { + DRM_ERROR("Failed to transfer edid to sram, ret=%d\n", ret); + return ret; + } + + /* Enable EDID emulation and select the desired EDID */ + val = 1 << PTN3460_EDID_ENABLE_EMULATION | + ptn_bridge->edid_emulation << PTN3460_EDID_EMULATION_SELECTION; + + ret = ptn3460_write_byte(ptn_bridge, PTN3460_EDID_EMULATION_ADDR, val); + if (ret) { + DRM_ERROR("Failed to write edid value, ret=%d\n", ret); + return ret; + } + + return 0; +} + +static void ptn3460_pre_enable(struct drm_bridge *bridge) +{ + struct ptn3460_bridge *ptn_bridge = bridge->driver_private; + int ret; + + if (ptn_bridge->enabled) + return; + + if (gpio_is_valid(ptn_bridge->gpio_pd_n)) + gpio_set_value(ptn_bridge->gpio_pd_n, 1); + + if (gpio_is_valid(ptn_bridge->gpio_rst_n)) { + gpio_set_value(ptn_bridge->gpio_rst_n, 0); + udelay(10); + gpio_set_value(ptn_bridge->gpio_rst_n, 1); + } + + /* + * There's a bug in the PTN chip where it falsely asserts hotplug before + * it is fully functional. We're forced to wait for the maximum start up + * time specified in the chip's datasheet to make sure we're really up. + */ + msleep(90); + + ret = ptn3460_select_edid(ptn_bridge); + if (ret) + DRM_ERROR("Select edid failed ret=%d\n", ret); + + ptn_bridge->enabled = true; +} + +static void ptn3460_enable(struct drm_bridge *bridge) +{ +} + +static void ptn3460_disable(struct drm_bridge *bridge) +{ + struct ptn3460_bridge *ptn_bridge = bridge->driver_private; + + if (!ptn_bridge->enabled) + return; + + ptn_bridge->enabled = false; + + if (gpio_is_valid(ptn_bridge->gpio_rst_n)) + gpio_set_value(ptn_bridge->gpio_rst_n, 1); + + if (gpio_is_valid(ptn_bridge->gpio_pd_n)) + gpio_set_value(ptn_bridge->gpio_pd_n, 0); +} + +static void ptn3460_post_disable(struct drm_bridge *bridge) +{ +} + +void ptn3460_bridge_destroy(struct drm_bridge *bridge) +{ + struct ptn3460_bridge *ptn_bridge = bridge->driver_private; + + drm_bridge_cleanup(bridge); + if (gpio_is_valid(ptn_bridge->gpio_pd_n)) + gpio_free(ptn_bridge->gpio_pd_n); + if (gpio_is_valid(ptn_bridge->gpio_rst_n)) + gpio_free(ptn_bridge->gpio_rst_n); + /* Nothing else to free, we've got devm allocated memory */ +} + +struct drm_bridge_funcs ptn3460_bridge_funcs = { + .pre_enable = ptn3460_pre_enable, + .enable = ptn3460_enable, + .disable = ptn3460_disable, + .post_disable = ptn3460_post_disable, + .destroy = ptn3460_bridge_destroy, +}; + +int ptn3460_get_modes(struct drm_connector *connector) +{ + struct ptn3460_bridge *ptn_bridge; + u8 *edid; + int ret, num_modes; + bool power_off; + + ptn_bridge = container_of(connector, struct ptn3460_bridge, connector); + + if (ptn_bridge->edid) + return drm_add_edid_modes(connector, ptn_bridge->edid); + + power_off = !ptn_bridge->enabled; + ptn3460_pre_enable(ptn_bridge->bridge); + + edid = kmalloc(EDID_LENGTH, GFP_KERNEL); + if (!edid) { + DRM_ERROR("Failed to allocate edid\n"); + return 0; + } + + ret = ptn3460_read_bytes(ptn_bridge, PTN3460_EDID_ADDR, edid, + EDID_LENGTH); + if (ret) { + kfree(edid); + num_modes = 0; + goto out; + } + + ptn_bridge->edid = (struct edid *)edid; + drm_mode_connector_update_edid_property(connector, ptn_bridge->edid); + + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); + +out: + if (power_off) + ptn3460_disable(ptn_bridge->bridge); + + return num_modes; +} + +static int ptn3460_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +struct drm_encoder *ptn3460_best_encoder(struct drm_connector *connector) +{ + struct ptn3460_bridge *ptn_bridge; + + ptn_bridge = container_of(connector, struct ptn3460_bridge, connector); + + return ptn_bridge->encoder; +} + +struct drm_connector_helper_funcs ptn3460_connector_helper_funcs = { + .get_modes = ptn3460_get_modes, + .mode_valid = ptn3460_mode_valid, + .best_encoder = ptn3460_best_encoder, +}; + +enum drm_connector_status ptn3460_detect(struct drm_connector *connector, + bool force) +{ + return connector_status_connected; +} + +void ptn3460_connector_destroy(struct drm_connector *connector) +{ + drm_connector_cleanup(connector); +} + +struct drm_connector_funcs ptn3460_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = ptn3460_detect, + .destroy = ptn3460_connector_destroy, +}; + +int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder, + struct i2c_client *client, struct device_node *node) +{ + int ret; + struct drm_bridge *bridge; + struct ptn3460_bridge *ptn_bridge; + + bridge = devm_kzalloc(dev->dev, sizeof(*bridge), GFP_KERNEL); + if (!bridge) { + DRM_ERROR("Failed to allocate drm bridge\n"); + return -ENOMEM; + } + + ptn_bridge = devm_kzalloc(dev->dev, sizeof(*ptn_bridge), GFP_KERNEL); + if (!ptn_bridge) { + DRM_ERROR("Failed to allocate ptn bridge\n"); + return -ENOMEM; + } + + ptn_bridge->client = client; + ptn_bridge->encoder = encoder; + ptn_bridge->bridge = bridge; + ptn_bridge->gpio_pd_n = of_get_named_gpio(node, "powerdown-gpio", 0); + if (gpio_is_valid(ptn_bridge->gpio_pd_n)) { + ret = gpio_request_one(ptn_bridge->gpio_pd_n, + GPIOF_OUT_INIT_HIGH, "PTN3460_PD_N"); + if (ret) { + DRM_ERROR("Request powerdown-gpio failed (%d)\n", ret); + return ret; + } + } + + ptn_bridge->gpio_rst_n = of_get_named_gpio(node, "reset-gpio", 0); + if (gpio_is_valid(ptn_bridge->gpio_rst_n)) { + /* + * Request the reset pin low to avoid the bridge being + * initialized prematurely + */ + ret = gpio_request_one(ptn_bridge->gpio_rst_n, + GPIOF_OUT_INIT_LOW, "PTN3460_RST_N"); + if (ret) { + DRM_ERROR("Request reset-gpio failed (%d)\n", ret); + gpio_free(ptn_bridge->gpio_pd_n); + return ret; + } + } + + ret = of_property_read_u32(node, "edid-emulation", + &ptn_bridge->edid_emulation); + if (ret) { + DRM_ERROR("Can't read edid emulation value\n"); + goto err; + } + + ret = drm_bridge_init(dev, bridge, &ptn3460_bridge_funcs); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + goto err; + } + + bridge->driver_private = ptn_bridge; + encoder->bridge = bridge; + + ret = drm_connector_init(dev, &ptn_bridge->connector, + &ptn3460_connector_funcs, DRM_MODE_CONNECTOR_LVDS); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + goto err; + } + drm_connector_helper_add(&ptn_bridge->connector, + &ptn3460_connector_helper_funcs); + drm_sysfs_connector_add(&ptn_bridge->connector); + drm_mode_connector_attach_encoder(&ptn_bridge->connector, encoder); + + return 0; + +err: + if (gpio_is_valid(ptn_bridge->gpio_pd_n)) + gpio_free(ptn_bridge->gpio_pd_n); + if (gpio_is_valid(ptn_bridge->gpio_rst_n)) + gpio_free(ptn_bridge->gpio_rst_n); + return ret; +} diff --git a/include/drm/bridge/ptn3460.h b/include/drm/bridge/ptn3460.h new file mode 100644 index 0000000..8481816 --- /dev/null +++ b/include/drm/bridge/ptn3460.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef _DRM_BRIDGE_PTN3460_H_ +#define _DRM_BRIDGE_PTN3460_H_ + +struct drm_device; +struct drm_encoder; +struct i2c_client; +struct device_node; + +#ifdef CONFIG_DRM_PTN3460 + +int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder, + struct i2c_client *client, struct device_node *node); +#else + +static inline int ptn3460_init(struct drm_device *dev, + struct drm_encoder *encoder, struct i2c_client *client, + struct device_node *node) +{ + return 0; +} + +#endif + +#endif
This patch adds code to look for the ptn3460 in the device tree file on exynos initialization. If ptn node is found, the driver will initialize the ptn3460 driver and skip creating a DP connector (since the bridge driver will register its own connector).
Signed-off-by: Sean Paul seanpaul@chromium.org ---
v2: - Changed include from of_i2c.h to i2c.h - Changed of_find_by_name to of_find_compatible
drivers/gpu/drm/exynos/exynos_drm_core.c | 44 +++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_core.c b/drivers/gpu/drm/exynos/exynos_drm_core.c index 1bef6dc..08ca4f9 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_core.c +++ b/drivers/gpu/drm/exynos/exynos_drm_core.c @@ -12,7 +12,9 @@ * option) any later version. */
+#include <linux/i2c.h> #include <drm/drmP.h> +#include <drm/bridge/ptn3460.h> #include "exynos_drm_drv.h" #include "exynos_drm_encoder.h" #include "exynos_drm_connector.h" @@ -20,6 +22,40 @@
static LIST_HEAD(exynos_drm_subdrv_list);
+struct bridge_init { + struct i2c_client *client; + struct device_node *node; +}; + +static bool find_bridge(const char *compat, struct bridge_init *bridge) +{ + bridge->client = NULL; + bridge->node = of_find_compatible_node(NULL, NULL, compat); + if (!bridge->node) + return false; + + bridge->client = of_find_i2c_device_by_node(bridge->node); + if (!bridge->client) + return false; + + return true; +} + +/* returns the number of bridges attached */ +static int exynos_drm_attach_lcd_bridge(struct drm_device *dev, + struct drm_encoder *encoder) +{ + struct bridge_init bridge; + int ret; + + if (find_bridge("nxp,ptn3460", &bridge)) { + ret = ptn3460_init(dev, encoder, bridge.client, bridge.node); + if (!ret) + return 1; + } + return 0; +} + static int exynos_drm_create_enc_conn(struct drm_device *dev, struct exynos_drm_subdrv *subdrv) { @@ -36,6 +72,13 @@ static int exynos_drm_create_enc_conn(struct drm_device *dev, DRM_ERROR("failed to create encoder\n"); return -EFAULT; } + subdrv->encoder = encoder; + + if (subdrv->manager->display_ops->type == EXYNOS_DISPLAY_TYPE_LCD) { + ret = exynos_drm_attach_lcd_bridge(dev, encoder); + if (ret) + return 0; + }
/* * create and initialize a connector for this sub driver and @@ -48,7 +91,6 @@ static int exynos_drm_create_enc_conn(struct drm_device *dev, goto err_destroy_encoder; }
- subdrv->encoder = encoder; subdrv->connector = connector;
return 0;
2013/10/4 Sean Paul seanpaul@chromium.org:
Then, shouldn't the lvds-bridge driver be implemented as i2c driver so that such tricky isn't needed? Is there any reason you use such tricky codes?
I think you need to implement the lvds-bridge driver as i2c driver, and then consider how to take care of this. I mean let's find a better way how we could take care of the i2c based driver in exynos drm framework or driver. In all cases, such tricky codes are not good.
On Thu, Oct 3, 2013 at 10:29 PM, Inki Dae inki.dae@samsung.com wrote:
This is what I was explaining earlier today. If the bridge driver is just an i2c driver, it won't get a pointer to drm_device, and can't register itself as a drm_bridge or drm_connector. To solve this, you need the ptn3460_init callback.
If it's an i2c driver with the ptn3460_init callback, where it parses the dt in probe, then you have a race between the ptn probe and the ptn init/drm hooks. ie: it's possible drm will initialize and call disable/post_disable/pre_enable/enable before things have been probed. To solve this, you need to use some global state and/or locking.
So, to summarize. We can have this of_find_compatible with a init callback, or we can have an i2c driver + global state/locking + init callback. I chose the former, since it seemed easier.
If you have a better way to achieve this, I'm definitely interested, but I saw this as the lesser of two evils.
Sean
2013/10/4 Sean Paul seanpaul@chromium.org:
No, I think you can use sub driver. how about registering to exynos drm core that driver using exynos_drm_subdrv_register function after probed? Is there something I am missing?
And also, exynos drm core will call a probe callback of the sub driver after _drm is initialized_. Is there something I am missing?
On Fri, Oct 4, 2013 at 12:18 AM, Inki Dae inki.dae@samsung.com wrote:
The ptn driver isn't exynos-specific, so I don't think making it an exynos_drm_subdrv is appropriate.
Sean
I _really know_ that the ptn driver isn't exynos-specific. I mean that you can use exynos_drm_subdrv somehow. ie. you can add a new bridge module specific to exynos, and this module can register its own sub driver at end of probe. Why do you try to use such tricky codes?
Thanks, Inki Dae
*subdrv)
On Fri, Oct 4, 2013 at 11:01 AM, Inki Dae inki.dae@samsung.com wrote:
So I create a new exynos_lvds_driver which is an i2c driver. When that probes, all it does is register that driver as an exynos_drm_subdrv. Then in the subdrv probe I call into ptn3460_init?
That seems a lot more tricky than just calling ptn3460_init directly...
Sean
compat);
Why more tricky? At least the dt binding will be done in device driver.
Anyway, this also is reasonable to me. It seems that we need a bit different design for such bridge driver.
Basically, exysnos drm fimd driver has one encoder and one connector, and these are connected each other, and this connector means lcd panel without any display bus. So if we want to use display bus, it might need to create a new encoder and connector for the display bus device. It seems that this way is more reasonable to me. ie. if we want for image data goes from fimd to lcd panel, we can connect a existing connector and crtc of fimd, and if fimd to display bus, we can connect the new connector and the crtc of fimd through setcrtc or setplane.
Of course, for this we would need more works and efforts. Such tricky codes definitely are not good.
Thanks, Inki Dae
and
This patch adds a node for the ptn3460 DP-LVDS chip in the exynos5250-snow board dts file.
Signed-off-by: Sean Paul seanpaul@chromium.org ---
v2: Changed node name to lvds-bridge
arch/arm/boot/dts/exynos5250-snow.dts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+)
diff --git a/arch/arm/boot/dts/exynos5250-snow.dts b/arch/arm/boot/dts/exynos5250-snow.dts index f1cf68e..2870205 100644 --- a/arch/arm/boot/dts/exynos5250-snow.dts +++ b/arch/arm/boot/dts/exynos5250-snow.dts @@ -33,6 +33,13 @@ sd3_bus4: sd3-bus-width4 { samsung,pin-drv = <0>; }; + + ptn3460_gpios: ptn3460-gpios { + samsung,pins = "gpy2-5", "gpx1-5"; + samsung,pin-function = <1>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; };
gpio-keys { @@ -186,6 +193,18 @@ samsung,vbus-gpio = <&gpx1 1 0>; };
+ i2c@12CD0000 { + lvds-bridge@20 { + compatible = "nxp,ptn3460"; + reg = <0x20>; + powerdown-gpio = <&gpy2 5 0>; + reset-gpio = <&gpx1 5 0>; + edid-emulation = <5>; + pinctrl-names = "default"; + pinctrl-0 = <&ptn3460_gpios>; + }; + }; + dp-controller@145B0000 { samsung,color-space = <0>; samsung,dynamic-range = <0>;
On Thu, Oct 3, 2013 at 3:28 PM, Sean Paul seanpaul@chromium.org wrote:
Series:
Acked-by: Olof Johansson olof@lixom.net
-Olof
This set adds some missing devicetree nodes to the exynos5250-snow file as well as adds a drm_bridge driver for the ptn3460 DP-LVDS chip. This chip is used in the exynos5250-snow board.
Sean
Sean Paul (5): ARM: dts: Add fimd display-timings for exynos5250-snow ARM: dts: Add dp-controller node to exynos5250-snow drm/bridge: Add PTN3460 bridge driver drm/exynos: Initialize ptn3460 if present ARM: dts: Add ptn3460 to exynos5250-snow
Documentation/devicetree/bindings/drm/bridge/ptn3460.txt | 27 ++++++++++++++++ arch/arm/boot/dts/exynos5250-snow.dts | 50 ++++++++++++++++ drivers/gpu/drm/Kconfig | 2 ++ drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/bridge/Kconfig | 4 +++ drivers/gpu/drm/bridge/Makefile | 3 ++ drivers/gpu/drm/bridge/ptn3460.c | 349 ++++++++++++++++ drivers/gpu/drm/exynos/exynos_drm_core.c | 44 ++++++++++++++++ include/drm/bridge/ptn3460.h | 37 ++++++++++++++++
This patch adds the internal panel timings to the exynos5250-snow board dts file.
Signed-off-by: Sean Paul seanpaul@chromium.org ---
v2: No difference v3: Added status = "okay"
arch/arm/boot/dts/exynos5250-snow.dts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+)
diff --git a/arch/arm/boot/dts/exynos5250-snow.dts b/arch/arm/boot/dts/exynos5250-snow.dts index fd711e2..28eea9b 100644 --- a/arch/arm/boot/dts/exynos5250-snow.dts +++ b/arch/arm/boot/dts/exynos5250-snow.dts @@ -186,6 +186,24 @@ samsung,vbus-gpio = <&gpx1 1 0>; };
+ fimd: fimd@14400000 { + status = "okay"; + display-timings { + native-mode = <&lcd_timing>; + lcd_timing: 1366x768 { + clock-frequency = <70589280>; + hactive = <1366>; + vactive = <768>; + hfront-porch = <40>; + hback-porch = <40>; + hsync-len = <32>; + vback-porch = <10>; + vfront-porch = <12>; + vsync-len = <6>; + }; + }; + }; + fixed-rate-clocks { xxti { compatible = "samsung,clock-xxti";
This patch adds the dp-controller node to the exynos5250-snow board dts file.
Signed-off-by: Sean Paul seanpaul@chromium.org ---
v2: Added dp-controller address to node (rebased on linux-next) v3: Added status = "okay"
arch/arm/boot/dts/exynos5250-snow.dts | 13 +++++++++++++ 1 file changed, 13 insertions(+)
diff --git a/arch/arm/boot/dts/exynos5250-snow.dts b/arch/arm/boot/dts/exynos5250-snow.dts index 28eea9b..f813644 100644 --- a/arch/arm/boot/dts/exynos5250-snow.dts +++ b/arch/arm/boot/dts/exynos5250-snow.dts @@ -186,6 +186,19 @@ samsung,vbus-gpio = <&gpx1 1 0>; };
+ dp-controller@145B0000 { + samsung,color-space = <0>; + samsung,dynamic-range = <0>; + samsung,ycbcr-coeff = <0>; + samsung,color-depth = <1>; + samsung,link-rate = <0x0a>; + samsung,lane-count = <2>; + + pinctrl-names = "default"; + pinctrl-0 = <&dp_hpd>; + status = "okay"; + }; + fimd: fimd@14400000 { status = "okay"; display-timings {
This patch adds a drm_bridge driver for the PTN3460 DisplayPort to LVDS bridge chip.
Signed-off-by: Sean Paul seanpaul@chromium.org ---
v2: - Changed header definition to static inline - Changed dt node name to lvds-bridge v3: No changes
.../devicetree/bindings/drm/bridge/ptn3460.txt | 27 ++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/bridge/Kconfig | 4 + drivers/gpu/drm/bridge/Makefile | 3 + drivers/gpu/drm/bridge/ptn3460.c | 349 +++++++++++++++++++++ include/drm/bridge/ptn3460.h | 37 +++ 7 files changed, 423 insertions(+) create mode 100644 Documentation/devicetree/bindings/drm/bridge/ptn3460.txt create mode 100644 drivers/gpu/drm/bridge/Kconfig create mode 100644 drivers/gpu/drm/bridge/Makefile create mode 100644 drivers/gpu/drm/bridge/ptn3460.c create mode 100644 include/drm/bridge/ptn3460.h
diff --git a/Documentation/devicetree/bindings/drm/bridge/ptn3460.txt b/Documentation/devicetree/bindings/drm/bridge/ptn3460.txt new file mode 100644 index 0000000..52b93b2 --- /dev/null +++ b/Documentation/devicetree/bindings/drm/bridge/ptn3460.txt @@ -0,0 +1,27 @@ +ptn3460 bridge bindings + +Required properties: + - compatible: "nxp,ptn3460" + - reg: i2c address of the bridge + - powerdown-gpio: OF device-tree gpio specification + - reset-gpio: OF device-tree gpio specification + - edid-emulation: The EDID emulation entry to use + +-------+------------+------------------+ + | Value | Resolution | Description | + | 0 | 1024x768 | NXP Generic | + | 1 | 1920x1080 | NXP Generic | + | 2 | 1920x1080 | NXP Generic | + | 3 | 1600x900 | Samsung LTM200KT | + | 4 | 1920x1080 | Samsung LTM230HT | + | 5 | 1366x768 | NXP Generic | + | 6 | 1600x900 | ChiMei M215HGE | + +-------+------------+------------------+ + +Example: + lvds-bridge@20 { + compatible = "nxp,ptn3460"; + reg = <0x20>; + powerdown-gpio = <&gpy2 5 1 0 0>; + reset-gpio = <&gpx1 5 1 0 0>; + edid-emulation = <5>; + }; diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 955555d..cd7bfb3 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -236,3 +236,5 @@ source "drivers/gpu/drm/tilcdc/Kconfig" source "drivers/gpu/drm/qxl/Kconfig"
source "drivers/gpu/drm/msm/Kconfig" + +source "drivers/gpu/drm/bridge/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index f089adf..9234253 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -56,3 +56,4 @@ obj-$(CONFIG_DRM_TILCDC) += tilcdc/ obj-$(CONFIG_DRM_QXL) += qxl/ obj-$(CONFIG_DRM_MSM) += msm/ obj-y += i2c/ +obj-y += bridge/ diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig new file mode 100644 index 0000000..f8db069 --- /dev/null +++ b/drivers/gpu/drm/bridge/Kconfig @@ -0,0 +1,4 @@ +config DRM_PTN3460 + tristate "PTN3460 DP/LVDS bridge" + depends on DRM && I2C + ---help--- diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile new file mode 100644 index 0000000..b4733e1 --- /dev/null +++ b/drivers/gpu/drm/bridge/Makefile @@ -0,0 +1,3 @@ +ccflags-y := -Iinclude/drm + +obj-$(CONFIG_DRM_PTN3460) += ptn3460.o diff --git a/drivers/gpu/drm/bridge/ptn3460.c b/drivers/gpu/drm/bridge/ptn3460.c new file mode 100644 index 0000000..a9e5c1a --- /dev/null +++ b/drivers/gpu/drm/bridge/ptn3460.c @@ -0,0 +1,349 @@ +/* + * NXP PTN3460 DP/LVDS bridge driver + * + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 <linux/module.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/delay.h> + +#include "drmP.h" +#include "drm_edid.h" +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + +#include "bridge/ptn3460.h" + +#define PTN3460_EDID_ADDR 0x0 +#define PTN3460_EDID_EMULATION_ADDR 0x84 +#define PTN3460_EDID_ENABLE_EMULATION 0 +#define PTN3460_EDID_EMULATION_SELECTION 1 +#define PTN3460_EDID_SRAM_LOAD_ADDR 0x85 + +struct ptn3460_bridge { + struct drm_connector connector; + struct i2c_client *client; + struct drm_encoder *encoder; + struct drm_bridge *bridge; + struct edid *edid; + int gpio_pd_n; + int gpio_rst_n; + u32 edid_emulation; + bool enabled; +}; + +static int ptn3460_read_bytes(struct ptn3460_bridge *ptn_bridge, char addr, + u8 *buf, int len) +{ + int ret; + + ret = i2c_master_send(ptn_bridge->client, &addr, 1); + if (ret <= 0) { + DRM_ERROR("Failed to send i2c command, ret=%d\n", ret); + return ret; + } + + ret = i2c_master_recv(ptn_bridge->client, buf, len); + if (ret <= 0) { + DRM_ERROR("Failed to recv i2c data, ret=%d\n", ret); + return ret; + } + + return 0; +} + +static int ptn3460_write_byte(struct ptn3460_bridge *ptn_bridge, char addr, + char val) +{ + int ret; + char buf[2]; + + buf[0] = addr; + buf[1] = val; + + ret = i2c_master_send(ptn_bridge->client, buf, ARRAY_SIZE(buf)); + if (ret <= 0) { + DRM_ERROR("Failed to send i2c command, ret=%d\n", ret); + return ret; + } + + return 0; +} + +static int ptn3460_select_edid(struct ptn3460_bridge *ptn_bridge) +{ + int ret; + char val; + + /* Load the selected edid into SRAM (accessed at PTN3460_EDID_ADDR) */ + ret = ptn3460_write_byte(ptn_bridge, PTN3460_EDID_SRAM_LOAD_ADDR, + ptn_bridge->edid_emulation); + if (ret) { + DRM_ERROR("Failed to transfer edid to sram, ret=%d\n", ret); + return ret; + } + + /* Enable EDID emulation and select the desired EDID */ + val = 1 << PTN3460_EDID_ENABLE_EMULATION | + ptn_bridge->edid_emulation << PTN3460_EDID_EMULATION_SELECTION; + + ret = ptn3460_write_byte(ptn_bridge, PTN3460_EDID_EMULATION_ADDR, val); + if (ret) { + DRM_ERROR("Failed to write edid value, ret=%d\n", ret); + return ret; + } + + return 0; +} + +static void ptn3460_pre_enable(struct drm_bridge *bridge) +{ + struct ptn3460_bridge *ptn_bridge = bridge->driver_private; + int ret; + + if (ptn_bridge->enabled) + return; + + if (gpio_is_valid(ptn_bridge->gpio_pd_n)) + gpio_set_value(ptn_bridge->gpio_pd_n, 1); + + if (gpio_is_valid(ptn_bridge->gpio_rst_n)) { + gpio_set_value(ptn_bridge->gpio_rst_n, 0); + udelay(10); + gpio_set_value(ptn_bridge->gpio_rst_n, 1); + } + + /* + * There's a bug in the PTN chip where it falsely asserts hotplug before + * it is fully functional. We're forced to wait for the maximum start up + * time specified in the chip's datasheet to make sure we're really up. + */ + msleep(90); + + ret = ptn3460_select_edid(ptn_bridge); + if (ret) + DRM_ERROR("Select edid failed ret=%d\n", ret); + + ptn_bridge->enabled = true; +} + +static void ptn3460_enable(struct drm_bridge *bridge) +{ +} + +static void ptn3460_disable(struct drm_bridge *bridge) +{ + struct ptn3460_bridge *ptn_bridge = bridge->driver_private; + + if (!ptn_bridge->enabled) + return; + + ptn_bridge->enabled = false; + + if (gpio_is_valid(ptn_bridge->gpio_rst_n)) + gpio_set_value(ptn_bridge->gpio_rst_n, 1); + + if (gpio_is_valid(ptn_bridge->gpio_pd_n)) + gpio_set_value(ptn_bridge->gpio_pd_n, 0); +} + +static void ptn3460_post_disable(struct drm_bridge *bridge) +{ +} + +void ptn3460_bridge_destroy(struct drm_bridge *bridge) +{ + struct ptn3460_bridge *ptn_bridge = bridge->driver_private; + + drm_bridge_cleanup(bridge); + if (gpio_is_valid(ptn_bridge->gpio_pd_n)) + gpio_free(ptn_bridge->gpio_pd_n); + if (gpio_is_valid(ptn_bridge->gpio_rst_n)) + gpio_free(ptn_bridge->gpio_rst_n); + /* Nothing else to free, we've got devm allocated memory */ +} + +struct drm_bridge_funcs ptn3460_bridge_funcs = { + .pre_enable = ptn3460_pre_enable, + .enable = ptn3460_enable, + .disable = ptn3460_disable, + .post_disable = ptn3460_post_disable, + .destroy = ptn3460_bridge_destroy, +}; + +int ptn3460_get_modes(struct drm_connector *connector) +{ + struct ptn3460_bridge *ptn_bridge; + u8 *edid; + int ret, num_modes; + bool power_off; + + ptn_bridge = container_of(connector, struct ptn3460_bridge, connector); + + if (ptn_bridge->edid) + return drm_add_edid_modes(connector, ptn_bridge->edid); + + power_off = !ptn_bridge->enabled; + ptn3460_pre_enable(ptn_bridge->bridge); + + edid = kmalloc(EDID_LENGTH, GFP_KERNEL); + if (!edid) { + DRM_ERROR("Failed to allocate edid\n"); + return 0; + } + + ret = ptn3460_read_bytes(ptn_bridge, PTN3460_EDID_ADDR, edid, + EDID_LENGTH); + if (ret) { + kfree(edid); + num_modes = 0; + goto out; + } + + ptn_bridge->edid = (struct edid *)edid; + drm_mode_connector_update_edid_property(connector, ptn_bridge->edid); + + num_modes = drm_add_edid_modes(connector, ptn_bridge->edid); + +out: + if (power_off) + ptn3460_disable(ptn_bridge->bridge); + + return num_modes; +} + +static int ptn3460_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +struct drm_encoder *ptn3460_best_encoder(struct drm_connector *connector) +{ + struct ptn3460_bridge *ptn_bridge; + + ptn_bridge = container_of(connector, struct ptn3460_bridge, connector); + + return ptn_bridge->encoder; +} + +struct drm_connector_helper_funcs ptn3460_connector_helper_funcs = { + .get_modes = ptn3460_get_modes, + .mode_valid = ptn3460_mode_valid, + .best_encoder = ptn3460_best_encoder, +}; + +enum drm_connector_status ptn3460_detect(struct drm_connector *connector, + bool force) +{ + return connector_status_connected; +} + +void ptn3460_connector_destroy(struct drm_connector *connector) +{ + drm_connector_cleanup(connector); +} + +struct drm_connector_funcs ptn3460_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = ptn3460_detect, + .destroy = ptn3460_connector_destroy, +}; + +int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder, + struct i2c_client *client, struct device_node *node) +{ + int ret; + struct drm_bridge *bridge; + struct ptn3460_bridge *ptn_bridge; + + bridge = devm_kzalloc(dev->dev, sizeof(*bridge), GFP_KERNEL); + if (!bridge) { + DRM_ERROR("Failed to allocate drm bridge\n"); + return -ENOMEM; + } + + ptn_bridge = devm_kzalloc(dev->dev, sizeof(*ptn_bridge), GFP_KERNEL); + if (!ptn_bridge) { + DRM_ERROR("Failed to allocate ptn bridge\n"); + return -ENOMEM; + } + + ptn_bridge->client = client; + ptn_bridge->encoder = encoder; + ptn_bridge->bridge = bridge; + ptn_bridge->gpio_pd_n = of_get_named_gpio(node, "powerdown-gpio", 0); + if (gpio_is_valid(ptn_bridge->gpio_pd_n)) { + ret = gpio_request_one(ptn_bridge->gpio_pd_n, + GPIOF_OUT_INIT_HIGH, "PTN3460_PD_N"); + if (ret) { + DRM_ERROR("Request powerdown-gpio failed (%d)\n", ret); + return ret; + } + } + + ptn_bridge->gpio_rst_n = of_get_named_gpio(node, "reset-gpio", 0); + if (gpio_is_valid(ptn_bridge->gpio_rst_n)) { + /* + * Request the reset pin low to avoid the bridge being + * initialized prematurely + */ + ret = gpio_request_one(ptn_bridge->gpio_rst_n, + GPIOF_OUT_INIT_LOW, "PTN3460_RST_N"); + if (ret) { + DRM_ERROR("Request reset-gpio failed (%d)\n", ret); + gpio_free(ptn_bridge->gpio_pd_n); + return ret; + } + } + + ret = of_property_read_u32(node, "edid-emulation", + &ptn_bridge->edid_emulation); + if (ret) { + DRM_ERROR("Can't read edid emulation value\n"); + goto err; + } + + ret = drm_bridge_init(dev, bridge, &ptn3460_bridge_funcs); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + goto err; + } + + bridge->driver_private = ptn_bridge; + encoder->bridge = bridge; + + ret = drm_connector_init(dev, &ptn_bridge->connector, + &ptn3460_connector_funcs, DRM_MODE_CONNECTOR_LVDS); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + goto err; + } + drm_connector_helper_add(&ptn_bridge->connector, + &ptn3460_connector_helper_funcs); + drm_sysfs_connector_add(&ptn_bridge->connector); + drm_mode_connector_attach_encoder(&ptn_bridge->connector, encoder); + + return 0; + +err: + if (gpio_is_valid(ptn_bridge->gpio_pd_n)) + gpio_free(ptn_bridge->gpio_pd_n); + if (gpio_is_valid(ptn_bridge->gpio_rst_n)) + gpio_free(ptn_bridge->gpio_rst_n); + return ret; +} diff --git a/include/drm/bridge/ptn3460.h b/include/drm/bridge/ptn3460.h new file mode 100644 index 0000000..8481816 --- /dev/null +++ b/include/drm/bridge/ptn3460.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef _DRM_BRIDGE_PTN3460_H_ +#define _DRM_BRIDGE_PTN3460_H_ + +struct drm_device; +struct drm_encoder; +struct i2c_client; +struct device_node; + +#ifdef CONFIG_DRM_PTN3460 + +int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder, + struct i2c_client *client, struct device_node *node); +#else + +static inline int ptn3460_init(struct drm_device *dev, + struct drm_encoder *encoder, struct i2c_client *client, + struct device_node *node) +{ + return 0; +} + +#endif + +#endif
This patch adds code to look for the ptn3460 in the device tree file on exynos initialization. If ptn node is found, the driver will initialize the ptn3460 driver and skip creating a DP connector (since the bridge driver will register its own connector).
Signed-off-by: Sean Paul seanpaul@chromium.org ---
v2: - Changed include from of_i2c.h to i2c.h - Changed of_find_by_name to of_find_compatible v3: No changes
drivers/gpu/drm/exynos/exynos_drm_core.c | 44 +++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_core.c b/drivers/gpu/drm/exynos/exynos_drm_core.c index 1bef6dc..08ca4f9 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_core.c +++ b/drivers/gpu/drm/exynos/exynos_drm_core.c @@ -12,7 +12,9 @@ * option) any later version. */
+#include <linux/i2c.h> #include <drm/drmP.h> +#include <drm/bridge/ptn3460.h> #include "exynos_drm_drv.h" #include "exynos_drm_encoder.h" #include "exynos_drm_connector.h" @@ -20,6 +22,40 @@
static LIST_HEAD(exynos_drm_subdrv_list);
+struct bridge_init { + struct i2c_client *client; + struct device_node *node; +}; + +static bool find_bridge(const char *compat, struct bridge_init *bridge) +{ + bridge->client = NULL; + bridge->node = of_find_compatible_node(NULL, NULL, compat); + if (!bridge->node) + return false; + + bridge->client = of_find_i2c_device_by_node(bridge->node); + if (!bridge->client) + return false; + + return true; +} + +/* returns the number of bridges attached */ +static int exynos_drm_attach_lcd_bridge(struct drm_device *dev, + struct drm_encoder *encoder) +{ + struct bridge_init bridge; + int ret; + + if (find_bridge("nxp,ptn3460", &bridge)) { + ret = ptn3460_init(dev, encoder, bridge.client, bridge.node); + if (!ret) + return 1; + } + return 0; +} + static int exynos_drm_create_enc_conn(struct drm_device *dev, struct exynos_drm_subdrv *subdrv) { @@ -36,6 +72,13 @@ static int exynos_drm_create_enc_conn(struct drm_device *dev, DRM_ERROR("failed to create encoder\n"); return -EFAULT; } + subdrv->encoder = encoder; + + if (subdrv->manager->display_ops->type == EXYNOS_DISPLAY_TYPE_LCD) { + ret = exynos_drm_attach_lcd_bridge(dev, encoder); + if (ret) + return 0; + }
/* * create and initialize a connector for this sub driver and @@ -48,7 +91,6 @@ static int exynos_drm_create_enc_conn(struct drm_device *dev, goto err_destroy_encoder; }
- subdrv->encoder = encoder; subdrv->connector = connector;
return 0;
This patch adds a node for the ptn3460 DP-LVDS chip in the exynos5250-snow board dts file.
Signed-off-by: Sean Paul seanpaul@chromium.org ---
v2: Changed node name to lvds-bridge v3: No changes
arch/arm/boot/dts/exynos5250-snow.dts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+)
diff --git a/arch/arm/boot/dts/exynos5250-snow.dts b/arch/arm/boot/dts/exynos5250-snow.dts index f813644..8c92df8 100644 --- a/arch/arm/boot/dts/exynos5250-snow.dts +++ b/arch/arm/boot/dts/exynos5250-snow.dts @@ -33,6 +33,13 @@ sd3_bus4: sd3-bus-width4 { samsung,pin-drv = <0>; }; + + ptn3460_gpios: ptn3460-gpios { + samsung,pins = "gpy2-5", "gpx1-5"; + samsung,pin-function = <1>; + samsung,pin-pud = <0>; + samsung,pin-drv = <0>; + }; };
gpio-keys { @@ -186,6 +193,18 @@ samsung,vbus-gpio = <&gpx1 1 0>; };
+ i2c@12CD0000 { + lvds-bridge@20 { + compatible = "nxp,ptn3460"; + reg = <0x20>; + powerdown-gpio = <&gpy2 5 0>; + reset-gpio = <&gpx1 5 0>; + edid-emulation = <5>; + pinctrl-names = "default"; + pinctrl-0 = <&ptn3460_gpios>; + }; + }; + dp-controller@145B0000 { samsung,color-space = <0>; samsung,dynamic-range = <0>;
dri-devel@lists.freedesktop.org