diff mbox

drm/i2c: Add driver for PTN3460 LVDS/DP bridge

Message ID 1359050946-25061-1-git-send-email-seanpaul@chromium.org (mailing list archive)
State New, archived
Headers show

Commit Message

Sean Paul Jan. 24, 2013, 6:09 p.m. UTC
This patch adds the driver for the PTN3460 LVDS/DP bridge chip.

The driver allows the EDID emulation to be selected from device tree,
along with specifying the GPIOs driving powerdown and reset pins.

The chip has a bug in it such that when the powerdown and reset pins
are toggled, the hotplug line blips before the bridge is completely
ready. This forces us to wait for the maximum specified setup time
(90ms) before interacting with the chip via i2c or doing DP training, as
opposed to watching the hotplug line. This limitation means that we need
to synchronize the bridge driver with the DP driver via the
ptn3460_wait_until_ready function.

Signed-off-by: Sean Paul <seanpaul@chromium.org>
---
 .../devicetree/bindings/drm/i2c/ptn3460.txt        |   27 ++
 drivers/gpu/drm/Kconfig                            |    2 +
 drivers/gpu/drm/i2c/Kconfig                        |    6 +
 drivers/gpu/drm/i2c/Makefile                       |    2 +
 drivers/gpu/drm/i2c/ptn3460.c                      |  283 ++++++++++++++++++++
 include/drm/i2c/ptn3460.h                          |   19 ++
 6 files changed, 339 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/drm/i2c/ptn3460.txt
 create mode 100644 drivers/gpu/drm/i2c/Kconfig
 create mode 100644 drivers/gpu/drm/i2c/ptn3460.c
 create mode 100644 include/drm/i2c/ptn3460.h

Comments

Daniel Vetter Jan. 24, 2013, 9:22 p.m. UTC | #1
On Thu, Jan 24, 2013 at 7:09 PM, Sean Paul <seanpaul@chromium.org> wrote:
> This patch adds the driver for the PTN3460 LVDS/DP bridge chip.
>
> The driver allows the EDID emulation to be selected from device tree,
> along with specifying the GPIOs driving powerdown and reset pins.
>
> The chip has a bug in it such that when the powerdown and reset pins
> are toggled, the hotplug line blips before the bridge is completely
> ready. This forces us to wait for the maximum specified setup time
> (90ms) before interacting with the chip via i2c or doing DP training, as
> opposed to watching the hotplug line. This limitation means that we need
> to synchronize the bridge driver with the DP driver via the
> ptn3460_wait_until_ready function.
>
> Signed-off-by: Sean Paul <seanpaul@chromium.org>
> ---
>  .../devicetree/bindings/drm/i2c/ptn3460.txt        |   27 ++
>  drivers/gpu/drm/Kconfig                            |    2 +
>  drivers/gpu/drm/i2c/Kconfig                        |    6 +
>  drivers/gpu/drm/i2c/Makefile                       |    2 +
>  drivers/gpu/drm/i2c/ptn3460.c                      |  283 ++++++++++++++++++++

Afaict from reading through the code, this is just a bit of
special-purpose i2c code to control some hw. Imo you can just put that
into your driver directory, since currently all the drivers in drm/i2c
are drm_encoder_slaves (potentially shared between drivers), and I
think we should keep it at that.

Of course, if you plane to extend that to such an encoder slave
driver, then we could move it back.
-Daniel
Sean Paul Jan. 24, 2013, 9:39 p.m. UTC | #2
On Thu, Jan 24, 2013 at 4:22 PM, Daniel Vetter <daniel@ffwll.ch> wrote:
> On Thu, Jan 24, 2013 at 7:09 PM, Sean Paul <seanpaul@chromium.org> wrote:
>> This patch adds the driver for the PTN3460 LVDS/DP bridge chip.
>>
>> The driver allows the EDID emulation to be selected from device tree,
>> along with specifying the GPIOs driving powerdown and reset pins.
>>
>> The chip has a bug in it such that when the powerdown and reset pins
>> are toggled, the hotplug line blips before the bridge is completely
>> ready. This forces us to wait for the maximum specified setup time
>> (90ms) before interacting with the chip via i2c or doing DP training, as
>> opposed to watching the hotplug line. This limitation means that we need
>> to synchronize the bridge driver with the DP driver via the
>> ptn3460_wait_until_ready function.
>>
>> Signed-off-by: Sean Paul <seanpaul@chromium.org>
>> ---
>>  .../devicetree/bindings/drm/i2c/ptn3460.txt        |   27 ++
>>  drivers/gpu/drm/Kconfig                            |    2 +
>>  drivers/gpu/drm/i2c/Kconfig                        |    6 +
>>  drivers/gpu/drm/i2c/Makefile                       |    2 +
>>  drivers/gpu/drm/i2c/ptn3460.c                      |  283 ++++++++++++++++++++
>
> Afaict from reading through the code, this is just a bit of
> special-purpose i2c code to control some hw. Imo you can just put that
> into your driver directory, since currently all the drivers in drm/i2c
> are drm_encoder_slaves (potentially shared between drivers), and I
> think we should keep it at that.
>
> Of course, if you plane to extend that to such an encoder slave
> driver, then we could move it back.

Yep, right on, it's not using any drm specific bits at the moment. I
have it in drm since I was using from the exynos DP driver in the
chromium tree (which has been moved into drm to alleviate the power
on/suspend/resume ordering issues that exist). I'm happy to move it
out, but want to avoid needlessly having to move it back in at some
point (depending on where the DP driver ends up).

Apart from that, I think there's a chance that it can be expanded to a
drm_encoder_slave if things get more complex, or potentially
daisy-chained as a CDF display, but that remains to be seen.

Suggestions welcome!

Sean


> -Daniel
> --
> Daniel Vetter
> Software Engineer, Intel Corporation
> +41 (0) 79 365 57 48 - http://blog.ffwll.ch
Jani Nikula Jan. 25, 2013, 8:56 a.m. UTC | #3
On Thu, 24 Jan 2013, Sean Paul <seanpaul@chromium.org> wrote:
> +static int ptn3460_power_up(struct ptn3460_platform_data *pd)
> +{
> +	int ret;
> +
> +	if (pd->gpio_pd_n > 0)
> +		gpio_set_value(pd->gpio_pd_n, 1);

Hi Sean, just a random thing that caught my eye: last I checked 0 is a
valid GPIO number. You can use gpio_is_valid() on the GPIO numbers.


BR,
Jani.
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/drm/i2c/ptn3460.txt b/Documentation/devicetree/bindings/drm/i2c/ptn3460.txt
new file mode 100644
index 0000000..c1cd329
--- /dev/null
+++ b/Documentation/devicetree/bindings/drm/i2c/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 983201b..45006a8 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -212,3 +212,5 @@  source "drivers/gpu/drm/cirrus/Kconfig"
 source "drivers/gpu/drm/shmobile/Kconfig"
 
 source "drivers/gpu/drm/tegra/Kconfig"
+
+source "drivers/gpu/drm/i2c/Kconfig"
diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig
new file mode 100644
index 0000000..a015f61
--- /dev/null
+++ b/drivers/gpu/drm/i2c/Kconfig
@@ -0,0 +1,6 @@ 
+config DRM_PTN3460
+	tristate "PTN3460 DP/LVDS bridge"
+	depends on DRM && I2C
+	---help---
+	  Adds the driver for the NXP PTN3460 DP/LVDS bridge chip.
+
diff --git a/drivers/gpu/drm/i2c/Makefile b/drivers/gpu/drm/i2c/Makefile
index 9286256..392904c 100644
--- a/drivers/gpu/drm/i2c/Makefile
+++ b/drivers/gpu/drm/i2c/Makefile
@@ -3,5 +3,7 @@  ccflags-y := -Iinclude/drm
 ch7006-y := ch7006_drv.o ch7006_mode.o
 obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o
 
+obj-$(CONFIG_DRM_PTN3460) += ptn3460.o
+
 sil164-y := sil164_drv.o
 obj-$(CONFIG_DRM_I2C_SIL164) += sil164.o
diff --git a/drivers/gpu/drm/i2c/ptn3460.c b/drivers/gpu/drm/i2c/ptn3460.c
new file mode 100644
index 0000000..6716d52
--- /dev/null
+++ b/drivers/gpu/drm/i2c/ptn3460.c
@@ -0,0 +1,283 @@ 
+/*
+ * 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/pm.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+
+#include "drmP.h"
+
+#include "i2c/ptn3460.h"
+
+#define PTN3460_EDID_EMULATION_ADDR		0x84
+#define PTN3460_EDID_ENABLE_EMULATION		0
+#define PTN3460_EDID_EMULATION_SELECTION	1
+
+#define PTN3460_READY_BLOCK	0
+#define PTN3460_READY_UNBLOCK	1
+
+struct ptn3460_platform_data {
+	struct device *dev;
+	struct i2c_client *client;
+	u8 addr;
+	int gpio_pd_n;
+	int gpio_rst_n;
+	u32 edid_emulation;
+	struct delayed_work ptn_work;
+};
+
+static atomic_t ptn3460_ready;
+static wait_queue_head_t ptn3460_wait_queue;
+
+static void initialize_wait_queue_once(void)
+{
+	static atomic_t wait_queue_initialized;
+
+	if (!atomic_cmpxchg(&wait_queue_initialized, 0, 1))
+		init_waitqueue_head(&ptn3460_wait_queue);
+}
+
+int ptn3460_wait_until_ready(int timeout_ms)
+{
+	int ret;
+
+	if (!of_find_compatible_node(NULL, NULL, "nxp,ptn3460"))
+		return 0;
+
+	initialize_wait_queue_once();
+
+	ret = wait_event_timeout(ptn3460_wait_queue,
+			atomic_read(&ptn3460_ready) == PTN3460_READY_UNBLOCK,
+			msecs_to_jiffies(timeout_ms));
+	if (!ret) {
+		DRM_ERROR("Wait until ready timed out\n");
+		return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+static int ptn3460_write_byte(struct ptn3460_platform_data *pd, char addr,
+		char val)
+{
+	int ret;
+	char buf[2];
+
+	buf[0] = addr;
+	buf[1] = val;
+
+	ret = i2c_master_send(pd->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_init_platform_data_from_dt(struct i2c_client *client)
+{
+	int ret;
+	struct ptn3460_platform_data *pd;
+	struct device *dev = &client->dev;
+
+	dev->platform_data = devm_kzalloc(dev,
+				sizeof(struct ptn3460_platform_data),
+				GFP_KERNEL);
+	if (!dev->platform_data) {
+		DRM_ERROR("Can't allocate platform data\n");
+		return -ENOMEM;
+	}
+	pd = dev->platform_data;
+	pd->dev = dev;
+	pd->client = client;
+
+	/* Fill platform data with device tree data */
+	pd->gpio_pd_n = of_get_named_gpio(dev->of_node, "powerdown-gpio", 0);
+	pd->gpio_rst_n = of_get_named_gpio(dev->of_node, "reset-gpio", 0);
+
+	ret = of_property_read_u32(dev->of_node, "edid-emulation",
+			&pd->edid_emulation);
+	if (ret) {
+		DRM_ERROR("Can't read edid emulation value\n");
+		return ret;
+	}
+
+	return 0;
+
+}
+
+static int ptn3460_select_edid(struct ptn3460_platform_data *pd)
+{
+	int ret;
+	char val;
+
+	val = 1 << PTN3460_EDID_ENABLE_EMULATION |
+		pd->edid_emulation << PTN3460_EDID_EMULATION_SELECTION;
+
+	ret = ptn3460_write_byte(pd, 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_work(struct work_struct *work)
+{
+	struct ptn3460_platform_data *pd =
+		container_of(work, struct ptn3460_platform_data, ptn_work.work);
+	int ret;
+
+	if (!pd) {
+		DRM_ERROR("pd is null\n");
+		return;
+	}
+
+	ret = ptn3460_select_edid(pd);
+	if (ret)
+		DRM_ERROR("Select edid failed ret=%d\n", ret);
+
+	atomic_set(&ptn3460_ready, PTN3460_READY_UNBLOCK);
+	wake_up(&ptn3460_wait_queue);
+}
+
+static int ptn3460_power_up(struct ptn3460_platform_data *pd)
+{
+	int ret;
+
+	if (pd->gpio_pd_n > 0)
+		gpio_set_value(pd->gpio_pd_n, 1);
+
+	if (pd->gpio_rst_n > 0) {
+		gpio_set_value(pd->gpio_rst_n, 0);
+		udelay(10);
+		gpio_set_value(pd->gpio_rst_n, 1);
+	}
+
+	ret = schedule_delayed_work(&pd->ptn_work, msecs_to_jiffies(90));
+	if (ret < 0)
+		DRM_ERROR("Could not schedule work ret=%d\n", ret);
+
+	return 0;
+}
+
+static int ptn3460_power_down(struct ptn3460_platform_data *pd)
+{
+	if (work_pending(&pd->ptn_work.work))
+		flush_work(&pd->ptn_work.work);
+
+	atomic_set(&ptn3460_ready, PTN3460_READY_BLOCK);
+
+	if (pd->gpio_rst_n > 0)
+		gpio_set_value(pd->gpio_rst_n, 1);
+
+	if (pd->gpio_pd_n > 0)
+		gpio_set_value(pd->gpio_pd_n, 0);
+
+	return 0;
+}
+
+int ptn3460_suspend(struct device *dev)
+{
+	return ptn3460_power_down(dev->platform_data);
+}
+
+int ptn3460_resume(struct device *dev)
+{
+	return ptn3460_power_up(dev->platform_data);
+}
+
+int ptn3460_probe(struct i2c_client *client, const struct i2c_device_id *device)
+{
+	struct device *dev = &client->dev;
+	struct ptn3460_platform_data *pd;
+	int ret;
+
+	ret = ptn3460_init_platform_data_from_dt(client);
+	if (ret)
+		return ret;
+	pd = dev->platform_data;
+
+	if (pd->gpio_pd_n > 0) {
+		ret = gpio_request_one(pd->gpio_pd_n, GPIOF_OUT_INIT_HIGH,
+					"PTN3460_PD_N");
+		if (ret)
+			goto err_pd;
+	}
+	if (pd->gpio_rst_n > 0) {
+		/*
+		 * Request the reset pin low to avoid the bridge being
+		 * initialized prematurely
+		 */
+		ret = gpio_request_one(pd->gpio_rst_n, GPIOF_OUT_INIT_LOW,
+					"PTN3460_RST_N");
+		if (ret)
+			goto err_pd;
+	}
+
+	initialize_wait_queue_once();
+
+	INIT_DELAYED_WORK(&pd->ptn_work, ptn3460_work);
+
+	ret = ptn3460_power_up(pd);
+	if (ret)
+		goto err_pd;
+
+	return 0;
+
+err_pd:
+	devm_kfree(dev, pd);
+	return ret;
+}
+
+int ptn3460_remove(struct i2c_client *client)
+{
+	struct ptn3460_platform_data *pd = client->dev.platform_data;
+
+	if (!pd)
+		return 0;
+
+	if (work_pending(&pd->ptn_work.work))
+		flush_work(&pd->ptn_work.work);
+
+	devm_kfree(&client->dev, pd);
+	return 0;
+}
+
+static const struct i2c_device_id ptn3460_ids[] = {
+	{ "ptn3460", 0 },
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, ptn3460_ids);
+
+static SIMPLE_DEV_PM_OPS(ptn3460_pm_ops, ptn3460_suspend, ptn3460_resume);
+
+static struct i2c_driver ptn3460_driver = {
+	.id_table	= ptn3460_ids,
+	.probe          = ptn3460_probe,
+	.remove         = ptn3460_remove,
+	.driver		= {
+		.name	= "ptn3460",
+		.owner	= THIS_MODULE,
+		.pm	= &ptn3460_pm_ops,
+	},
+};
+module_i2c_driver(ptn3460_driver);
diff --git a/include/drm/i2c/ptn3460.h b/include/drm/i2c/ptn3460.h
new file mode 100644
index 0000000..778b21a
--- /dev/null
+++ b/include/drm/i2c/ptn3460.h
@@ -0,0 +1,19 @@ 
+/*
+ * 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_I2C_PTN3460_H_
+#define _DRM_I2C_PTN3460_H_
+
+int ptn3460_wait_until_ready(int timeout_ms);
+
+#endif