All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] drm/i2c: Add driver for PTN3460 LVDS/DP bridge
@ 2013-01-24 18:09 Sean Paul
       [not found] ` <1359050946-25061-1-git-send-email-seanpaul-F7+t8E8rja9g9hUCZPvPmw@public.gmane.org>
  2013-01-25  8:56 ` Jani Nikula
  0 siblings, 2 replies; 5+ messages in thread
From: Sean Paul @ 2013-01-24 18:09 UTC (permalink / raw)
  To: devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW

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-F7+t8E8rja9g9hUCZPvPmw@public.gmane.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

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
-- 
1.7.7.3

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH] drm/i2c: Add driver for PTN3460 LVDS/DP bridge
       [not found] ` <1359050946-25061-1-git-send-email-seanpaul-F7+t8E8rja9g9hUCZPvPmw@public.gmane.org>
@ 2013-01-24 21:22   ` Daniel Vetter
       [not found]     ` <CAKMK7uGQredcS=rqH+m8uDKb72mszOxY6HPjcJpj5g0AwhHvOw-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 5+ messages in thread
From: Daniel Vetter @ 2013-01-24 21:22 UTC (permalink / raw)
  To: Sean Paul
  Cc: devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW

On Thu, Jan 24, 2013 at 7:09 PM, Sean Paul <seanpaul-F7+t8E8rja9g9hUCZPvPmw@public.gmane.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-F7+t8E8rja9g9hUCZPvPmw@public.gmane.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
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] drm/i2c: Add driver for PTN3460 LVDS/DP bridge
       [not found]     ` <CAKMK7uGQredcS=rqH+m8uDKb72mszOxY6HPjcJpj5g0AwhHvOw-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2013-01-24 21:39       ` Sean Paul
  0 siblings, 0 replies; 5+ messages in thread
From: Sean Paul @ 2013-01-24 21:39 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW

On Thu, Jan 24, 2013 at 4:22 PM, Daniel Vetter <daniel-/w4YWyX8dFk@public.gmane.org> wrote:
> On Thu, Jan 24, 2013 at 7:09 PM, Sean Paul <seanpaul-F7+t8E8rja9g9hUCZPvPmw@public.gmane.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-F7+t8E8rja9g9hUCZPvPmw@public.gmane.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

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] drm/i2c: Add driver for PTN3460 LVDS/DP bridge
  2013-01-24 18:09 [PATCH] drm/i2c: Add driver for PTN3460 LVDS/DP bridge Sean Paul
       [not found] ` <1359050946-25061-1-git-send-email-seanpaul-F7+t8E8rja9g9hUCZPvPmw@public.gmane.org>
@ 2013-01-25  8:56 ` Jani Nikula
       [not found]   ` <87bocdtzcb.fsf-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
  1 sibling, 1 reply; 5+ messages in thread
From: Jani Nikula @ 2013-01-25  8:56 UTC (permalink / raw)
  To: Sean Paul, devicetree-discuss, dri-devel

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.

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v2] drm/i2c: Add driver for PTN3460 LVDS/DP bridge
       [not found]   ` <87bocdtzcb.fsf-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
@ 2013-01-28 21:43     ` Sean Paul
  0 siblings, 0 replies; 5+ messages in thread
From: Sean Paul @ 2013-01-28 21:43 UTC (permalink / raw)
  To: jani.nikula-VuQAYsv1563Yd54FQh9/CA, daniel-/w4YWyX8dFk
  Cc: devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW

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-F7+t8E8rja9g9hUCZPvPmw@public.gmane.org>
---
Thanks for the review, Jani. I've updated the patch to use gpio_is_valid().

 .../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

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..c2e34dc
--- /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 (gpio_is_valid(pd->gpio_pd_n))
+		gpio_set_value(pd->gpio_pd_n, 1);
+
+	if (gpio_is_valid(pd->gpio_rst_n)) {
+		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 (gpio_is_valid(pd->gpio_rst_n))
+		gpio_set_value(pd->gpio_rst_n, 1);
+
+	if (gpio_is_valid(pd->gpio_pd_n))
+		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 (gpio_is_valid(pd->gpio_pd_n)) {
+		ret = gpio_request_one(pd->gpio_pd_n, GPIOF_OUT_INIT_HIGH,
+					"PTN3460_PD_N");
+		if (ret)
+			goto err_pd;
+	}
+	if (gpio_is_valid(pd->gpio_rst_n)) {
+		/*
+		 * 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
-- 
1.7.7.3

^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2013-01-28 21:43 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-01-24 18:09 [PATCH] drm/i2c: Add driver for PTN3460 LVDS/DP bridge Sean Paul
     [not found] ` <1359050946-25061-1-git-send-email-seanpaul-F7+t8E8rja9g9hUCZPvPmw@public.gmane.org>
2013-01-24 21:22   ` Daniel Vetter
     [not found]     ` <CAKMK7uGQredcS=rqH+m8uDKb72mszOxY6HPjcJpj5g0AwhHvOw-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2013-01-24 21:39       ` Sean Paul
2013-01-25  8:56 ` Jani Nikula
     [not found]   ` <87bocdtzcb.fsf-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
2013-01-28 21:43     ` [PATCH v2] " Sean Paul

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.