diff options
author | Sean Paul <seanpaul@chromium.org> | 2014-02-24 14:31:24 +0400 |
---|---|---|
committer | Inki Dae <daeinki@gmail.com> | 2014-03-23 19:36:37 +0400 |
commit | a9fe713d7d45c639604420b56c59ca5fd479731b (patch) | |
tree | 3f5a540344b16dc3339880d90d8fd8e4697f92e5 /drivers/gpu/drm | |
parent | ce6cb556c9fc95d69c661f8da0e3e410a4e6565a (diff) | |
download | linux-a9fe713d7d45c639604420b56c59ca5fd479731b.tar.xz |
drm/bridge: Add PTN3460 bridge driver
This patch adds a drm_bridge driver for the PTN3460 DisplayPort to LVDS
bridge chip.
Signed-off-by: Sean Paul <seanpaul@chromium.org>
Signed-off-by: Inki Dae <inki.dae@samsung.com>
Diffstat (limited to 'drivers/gpu/drm')
-rw-r--r-- | drivers/gpu/drm/Kconfig | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/Kconfig | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/Makefile | 3 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/ptn3460.c | 349 |
5 files changed, 359 insertions, 0 deletions
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 8e7fa4dbaed8..d1cc2f613a78 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -199,3 +199,5 @@ source "drivers/gpu/drm/msm/Kconfig" source "drivers/gpu/drm/tegra/Kconfig" source "drivers/gpu/drm/panel/Kconfig" + +source "drivers/gpu/drm/bridge/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 292a79d64146..5e792b0a5f75 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -63,3 +63,4 @@ obj-$(CONFIG_DRM_MSM) += msm/ obj-$(CONFIG_DRM_TEGRA) += tegra/ obj-y += i2c/ obj-y += panel/ +obj-y += bridge/ diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig new file mode 100644 index 000000000000..f8db06959ae8 --- /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 000000000000..b4733e1fbd2e --- /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 000000000000..a9e5c1a13666 --- /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; +} |