From patchwork Mon Oct 17 16:33:29 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marek Vasut X-Patchwork-Id: 9380967 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 34F856086B for ; Tue, 18 Oct 2016 01:14:08 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 44FFE28F7C for ; Tue, 18 Oct 2016 01:14:08 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 38BA828FF4; Tue, 18 Oct 2016 01:14:08 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-4.2 required=2.0 tests=BAYES_00, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 21C5B28FF3 for ; Tue, 18 Oct 2016 01:14:07 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id F0FA26E60C; Tue, 18 Oct 2016 01:14:04 +0000 (UTC) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from mail-out.m-online.net (mail-out.m-online.net [212.18.0.9]) by gabe.freedesktop.org (Postfix) with ESMTPS id EAEBC6E566 for ; Mon, 17 Oct 2016 16:33:44 +0000 (UTC) Received: from frontend01.mail.m-online.net (unknown [192.168.8.182]) by mail-out.m-online.net (Postfix) with ESMTP id 3syP0R4F2wz3hjJ7; Mon, 17 Oct 2016 18:33:43 +0200 (CEST) Received: from localhost (dynscan1.mnet-online.de [192.168.6.68]) by mail.m-online.net (Postfix) with ESMTP id 3syP0R3bwPzvkKJ; Mon, 17 Oct 2016 18:33:43 +0200 (CEST) X-Virus-Scanned: amavisd-new at mnet-online.de Received: from mail.mnet-online.de ([192.168.8.182]) by localhost (dynscan1.mail.m-online.net [192.168.6.68]) (amavisd-new, port 10024) with ESMTP id 68onCSgh1zWh; Mon, 17 Oct 2016 18:33:41 +0200 (CEST) X-Auth-Info: rFMK8diY8u3jegmaCt1Boz1+m91vJpRXq/ZtZ7yS+gw= Received: from chi.lan (unknown [195.140.253.167]) by mail.mnet-online.de (Postfix) with ESMTPA; Mon, 17 Oct 2016 18:33:41 +0200 (CEST) From: Marek Vasut To: dri-devel@lists.freedesktop.org Subject: [PATCH 3/3] drm/bridge: Add ITE IT6251 bridge driver Date: Mon, 17 Oct 2016 18:33:29 +0200 Message-Id: <20161017163329.11821-3-marex@denx.de> X-Mailer: git-send-email 2.9.3 In-Reply-To: <20161017163329.11821-1-marex@denx.de> References: <20161017163329.11821-1-marex@denx.de> X-Mailman-Approved-At: Tue, 18 Oct 2016 01:13:38 +0000 Cc: Marek Vasut , Daniel Vetter X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" X-Virus-Scanned: ClamAV using ClamSMTP Add driver for the ITE IT6251 LVDS-to-eDP bridge. Signed-off-by: Marek Vasut Cc: Daniel Vetter Cc: Sean Cross --- drivers/gpu/drm/bridge/Kconfig | 9 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ite-it6251.c | 606 ++++++++++++++++++++++++++++++++++++ 3 files changed, 616 insertions(+) create mode 100644 drivers/gpu/drm/bridge/ite-it6251.c diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 10e12e7..e9c96b9 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -39,6 +39,15 @@ config DRM_DW_HDMI_AHB_AUDIO Designware HDMI block. This is used in conjunction with the i.MX6 HDMI driver. +config DRM_ITE_IT6251 + tristate "ITE IT6251 LVDS/eDP bridge" + depends on OF + select DRM_KMS_HELPER + select DRM_PANEL + select REGMAP_I2C + ---help--- + ITE IT6251 LVDS-eDP bridge chip driver. + config DRM_NXP_PTN3460 tristate "NXP PTN3460 DP/LVDS bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index cdf3a3c..736dba7 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o obj-$(CONFIG_DRM_DUMB_VGA_DAC) += dumb-vga-dac.o obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o +obj-$(CONFIG_DRM_ITE_IT6251) += ite-it6251.o obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_SII902X) += sii902x.o diff --git a/drivers/gpu/drm/bridge/ite-it6251.c b/drivers/gpu/drm/bridge/ite-it6251.c new file mode 100644 index 0000000..a19bb4d --- /dev/null +++ b/drivers/gpu/drm/bridge/ite-it6251.c @@ -0,0 +1,606 @@ +/* + * Copyright (C) 2014 Sean Cross + * + * Rework for mainline: Marek Vasut + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "drmP.h" +#include "drm_crtc.h" +#include "drm_crtc_helper.h" +#include "drm_atomic_helper.h" + +struct it6251_bridge { + struct i2c_client *client; + struct i2c_client *lvds_client; + struct regmap *regmap; + struct regmap *lvds_regmap; + struct regulator *regulator; + + struct drm_connector connector; + struct drm_bridge bridge; + struct drm_panel *panel; +}; + +/* Register definitions */ +#define IT6251_VENDOR_ID_LOW 0x00 +#define IT6251_VENDOR_ID_HIGH 0x01 +#define IT6251_DEVICE_ID_LOW 0x02 +#define IT6251_DEVICE_ID_HIGH 0x03 +#define IT6251_SYSTEM_STATUS 0x0d +#define IT6251_SYSTEM_STATUS_RINTSTATUS BIT(0) +#define IT6251_SYSTEM_STATUS_RHPDSTATUS BIT(1) +#define IT6251_SYSTEM_STATUS_RVIDEOSTABLE BIT(2) +#define IT6251_SYSTEM_STATUS_RPLL_IOLOCK BIT(3) +#define IT6251_SYSTEM_STATUS_RPLL_XPLOCK BIT(4) +#define IT6251_SYSTEM_STATUS_RPLL_SPLOCK BIT(5) +#define IT6251_SYSTEM_STATUS_RAUXFREQ_LOCK BIT(6) +#define IT6251_REF_STATE 0x0e +#define IT6251_REF_STATE_MAIN_LINK_DISABLED BIT(0) +#define IT6251_REF_STATE_AUX_CHANNEL_READ BIT(1) +#define IT6251_REF_STATE_CR_PATTERN BIT(2) +#define IT6251_REF_STATE_EQ_PATTERN BIT(3) +#define IT6251_REF_STATE_NORMAL_OPERATION BIT(4) +#define IT6251_REF_STATE_MUTED BIT(5) +#define IT6251_RPCLK_CNT_LOW 0x13 +#define IT6251_RPCLK_CNT_HIGH 0x14 +#define IT6251_RPC_REQ 0x2b +#define IT6251_RPC_REQ_RPC_FIFOFULL BIT(6) +#define IT6251_RPC_REQ_RPC_FIFOEMPTY BIT(7) +#define IT6251_PCLK_CNT_LOW 0x57 +#define IT6251_PCLK_CNT_HIGH 0x58 +#define IT6251_DPHDEW_LOW 0xa5 +#define IT6251_DPHDEW_HIGH 0xa6 +#define IT6251_DPVDEW_LOW 0xaf +#define IT6251_DPVDEW_HIGH 0xb0 +#define IT6251_LVDS_PORT_ADDR 0xfd +#define IT6251_LVDS_PORT_CTRL 0xfe +#define IT6251_LVDS_PORT_CTRL_EN BIT(0) + +/* + * Register programming sequences. + * NOTE: There is a lot of registers here which are completely undocumented + * and/or their meaning is not clear from the little documentation + * that is available for this chip. These values below just seem to + * work well enough. + */ +static const struct reg_sequence it6251_lvds_rx_sequence[] = { + { 0x05, 0x00 }, + + { 0x3b, 0x42 }, /* reset LVDSRX PLL */ + { 0x3b, 0x43 }, + + { 0x3c, 0x08 }, /* something with SSC PLL */ + { 0x0b, 0x88 }, /* don't swap links, writing reserved regs */ + + { 0x2c, 0x01 }, /* JEIDA, 8-bit depth 0x11, original 0x42 */ + { 0x32, 0x04 }, /* "reserved" */ + { 0x35, 0xe0 }, /* "reserved" */ + { 0x2b, 0x24 }, /* "reserved" + clock delay */ + + { 0x05, 0x02 }, /* reset LVDSRX pix clock */ + { 0x05, 0x00 }, +}; + +static const struct reg_sequence it6251_edp_tx_sequence[] = { + /* two lane mode, normal operation, no swapping, no downspread */ + { 0x16, 0x02 }, + { 0x23, 0x40 }, /* some AUX channel EDID magic */ + { 0x5c, 0xf3 }, /* power down lanes 3-0 */ + { 0x5f, 0x06 }, /* enable DP scrambling, change EQ CR phase */ + { 0x60, 0x02 }, /* color mode RGB, pclk/2 */ + { 0x61, 0x04 }, /* dual pixel input mode, no EO swap, no RGB swap */ + { 0x62, 0x01 }, /* M444B24 video format */ + + /* vesa range / not interlace / vsync high / hsync high */ + { 0xa0, 0x0F }, + + { 0xc9, 0xf5 }, /* hpd event timer set to 1.6-ish ms */ + + { 0xca, 0x4d }, /* more reserved magic */ + { 0xcb, 0x37 }, + + /* enhanced framing mode, auto video fifo reset, video mute disable */ + { 0xd3, 0x03 }, + { 0xd4, 0x45 }, /* "vidstmp" and some reserved stuff */ + + { 0xe7, 0xa0 }, /* queue number -- reserved */ + { 0xe8, 0x33 }, /* info frame packets and reserved */ + { 0xec, 0x00 }, /* more AVI stuff */ + + { 0x23, 0x42 }, /* select PC master reg for aux channel? */ + + { 0x24, 0x00 }, /* send PC request commands */ + { 0x25, 0x00 }, + { 0x26, 0x00 }, + + { 0x2b, 0x00 }, /* native aux read */ + { 0x23, 0x40 }, /* back to internal */ + + { 0x19, 0xff }, /* voltage swing level 3 */ + { 0x1a, 0xff }, /* pre-emphasis level 3 */ + + { 0x17, 0x01 }, /* start link training */ +}; + +static struct it6251_bridge *bridge_to_it6251(struct drm_bridge *bridge) +{ + return container_of(bridge, struct it6251_bridge, bridge); +} + +static struct it6251_bridge *conn_to_it6251(struct drm_connector *connector) +{ + return container_of(connector, struct it6251_bridge, connector); +} + +static int it6251_is_stable(struct it6251_bridge *it6251) +{ + struct drm_display_mode *mode; + unsigned int status, rpclkcnt, clkcnt, refstate, rpcreq; + u8 regs[2]; + u16 hactive; + u16 vactive; + int ret; + + ret = regmap_read(it6251->regmap, IT6251_SYSTEM_STATUS, &status); + if (ret) + return ret; + dev_dbg(&it6251->client->dev, "System status: 0x%02x\n", status); + + if (!(status & IT6251_SYSTEM_STATUS_RVIDEOSTABLE)) + return -EINVAL; + + ret = regmap_bulk_read(it6251->regmap, IT6251_RPCLK_CNT_LOW, regs, 2); + if (ret) + return ret; + rpclkcnt = (regs[0] & 0xff) | ((regs[1] & 0x0f) << 8); + dev_dbg(&it6251->client->dev, "RPCLKCnt: %d\n", rpclkcnt); + + ret = regmap_bulk_read(it6251->lvds_regmap, IT6251_PCLK_CNT_LOW, + regs, 2); + if (ret) + return ret; + clkcnt = (regs[0] & 0xff) | ((regs[1] & 0x0f) << 8); + dev_dbg(&it6251->client->dev, "Clock: 0x%02x\n", clkcnt); + + ret = regmap_read(it6251->lvds_regmap, IT6251_REF_STATE, &refstate); + if (ret) + return ret; + dev_dbg(&it6251->client->dev, "Ref Link State: 0x%02x\n", refstate); + + ret = regmap_read(it6251->lvds_regmap, IT6251_RPC_REQ, &rpcreq); + if (ret) + return ret; + dev_dbg(&it6251->client->dev, "RPC Req: 0x%02x\n", rpcreq); + + ret = regmap_bulk_read(it6251->regmap, IT6251_DPHDEW_LOW, regs, 2); + if (ret) + return ret; + hactive = (regs[0] & 0xff) | ((regs[1] & 0x1f) << 8); + dev_dbg(&it6251->client->dev, "hactive: %d\n", hactive); + + ret = regmap_bulk_read(it6251->regmap, IT6251_DPVDEW_LOW, regs, 2); + if (ret) + return ret; + vactive = (regs[0] & 0xff) | ((regs[1] & 0x0f) << 8); + dev_dbg(&it6251->client->dev, "vactive: %d\n", vactive); + + if ((refstate & 0x1f) != 0) + return -EINVAL; + + if (rpcreq & IT6251_RPC_REQ_RPC_FIFOFULL) { + dev_err(&it6251->client->dev, + "RPC fifofull is set, might be an error\n"); + return -EINVAL; + } + + /* If video is muted, that's a failure */ + if (refstate & IT6251_REF_STATE_MUTED) + return -EINVAL; + + list_for_each_entry(mode, &it6251->panel->connector->modes, head) + if ((mode->hdisplay == hactive) && (mode->vdisplay == vactive)) + return 0; + + dev_info(&it6251->client->dev, "no mode match found\n"); + return -EINVAL; +} + +static int it6251_init(struct it6251_bridge *it6251) +{ + const struct reg_sequence it6251_reset_reg_sequence[] = { + { 0x05, 0x00 }, + { IT6251_LVDS_PORT_ADDR, it6251->lvds_client->addr << 1 }, + { IT6251_LVDS_PORT_CTRL, IT6251_LVDS_PORT_CTRL_EN }, + }; + + int ret, stable_delays; + unsigned int reg; + + /* + * Reset DisplayPort half. Setting bit 2 causes IT6251 to not + * respond over i2c, which is considered "normal". This write + * will report failure, but will actually succeed. + */ + regmap_write(it6251->regmap, 0x05, 0xff); + + /* Un-reset DisplayPort half and configure LVDS receiver. */ + ret = regmap_multi_reg_write(it6251->regmap, it6251_reset_reg_sequence, + ARRAY_SIZE(it6251_reset_reg_sequence)); + if (ret) { + dev_err(&it6251->client->dev, "cannot setup eDP half\n"); + return ret; + } + + /* LVDS RX */ + regmap_write(it6251->lvds_regmap, 0x05, 0xff); + ret = regmap_multi_reg_write(it6251->lvds_regmap, + it6251_lvds_rx_sequence, + ARRAY_SIZE(it6251_lvds_rx_sequence)); + if (ret) { + dev_err(&it6251->lvds_client->dev, "cannot setup LVDS RX\n"); + return ret; + } + + /* eDP TX */ + ret = regmap_multi_reg_write(it6251->regmap, + it6251_edp_tx_sequence, + ARRAY_SIZE(it6251_edp_tx_sequence)); + if (ret) { + dev_err(&it6251->client->dev, "cannot setup eDP TX\n"); + return ret; + } + + for (stable_delays = 0; stable_delays < 100; stable_delays++) { + ret = regmap_read(it6251->regmap, 0x0e, ®); + if (ret || ((reg & 0x1f) != 0x10)) { + mdelay(2); + continue; + } + + ret = regmap_read(it6251->regmap, IT6251_SYSTEM_STATUS, ®); + if (ret || !(reg & IT6251_SYSTEM_STATUS_RVIDEOSTABLE)) { + mdelay(2); + continue; + } + + break; + } + + /* + * If we couldn't stabilize, requeue and try again, because it means + * that the LVDS channel isn't stable yet. + */ + ret = it6251_is_stable(it6251); + if (ret) + dev_err(&it6251->client->dev, "bridge is not stable\n"); + + return ret; +} + +static int it6251_power_down(struct it6251_bridge *it6251) +{ + struct device *dev = &it6251->client->dev; + int ret = 0; + + if (regulator_is_enabled(it6251->regulator)) { + ret = regulator_disable(it6251->regulator); + if (ret) + dev_err(dev, "unable to disable regulator\n"); + } + + return ret; +} + +static int it6251_power_up(struct it6251_bridge *it6251) +{ + struct i2c_client *client = it6251->client; + u8 regs[4]; + int i, ret; + + ret = regulator_enable(it6251->regulator); + if (ret) { + dev_err(&client->dev, "unable to enable regulator\n"); + return ret; + } + + /* Sometimes it seems like multiple tries are needed */ + for (i = 0; i < 5; i++) { + ret = regmap_bulk_read(it6251->regmap, IT6251_VENDOR_ID_LOW, + regs, 4); + if (!ret && regs[0] && regs[1] && regs[2] && regs[3]) { + dev_info(&client->dev, "found ITE6251 [%04x:%04x]\n", + (regs[1] << 8) | regs[0], + (regs[3] << 8) | regs[2]); + return 0; + } + + usleep_range(100000, 200000); + } + + dev_err(&client->dev, "unable to read product id\n"); + it6251_power_down(it6251); + return -EINVAL; +} + +/* I2C driver functions */ +static void it6251_pre_enable(struct drm_bridge *bridge) +{ + struct it6251_bridge *it6251 = bridge_to_it6251(bridge); + + if (drm_panel_prepare(it6251->panel)) { + DRM_ERROR("failed to prepare panel\n"); + return; + } + + it6251_power_up(it6251); +} + +static void it6251_enable(struct drm_bridge *bridge) +{ + struct it6251_bridge *it6251 = bridge_to_it6251(bridge); + int tries, ret; + + if (drm_panel_enable(it6251->panel)) { + DRM_ERROR("failed to enable panel\n"); + return; + } + + for (tries = 0; tries < 5; tries++) { + ret = it6251_init(it6251); + if (!ret) + return; + + /* If the init failed, restart the chip */ + it6251_power_down(it6251); + it6251_power_up(it6251); + } +} + +static void it6251_disable(struct drm_bridge *bridge) +{ + struct it6251_bridge *it6251 = bridge_to_it6251(bridge); + + if (drm_panel_disable(it6251->panel)) + DRM_ERROR("failed to disable panel\n"); +} + +static void it6251_post_disable(struct drm_bridge *bridge) +{ + struct it6251_bridge *it6251 = bridge_to_it6251(bridge); + + if (drm_panel_unprepare(it6251->panel)) + DRM_ERROR("failed to unprepare panel\n"); + + it6251_power_down(it6251); +} + +static int it6251_get_modes(struct drm_connector *connector) +{ + struct it6251_bridge *it6251 = conn_to_it6251(connector); + + return drm_panel_get_modes(it6251->panel); +} + +static const struct drm_connector_helper_funcs it6251_connector_helper_funcs = { + .get_modes = it6251_get_modes, +}; + +static enum drm_connector_status it6251_detect(struct drm_connector *connector, + bool force) +{ + return connector_status_connected; +} + +static const struct drm_connector_funcs it6251_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = it6251_detect, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int it6251_attach(struct drm_bridge *bridge) +{ + struct it6251_bridge *it6251 = bridge_to_it6251(bridge); + int ret; + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + it6251->connector.polled = DRM_CONNECTOR_POLL_HPD; + ret = drm_connector_init(bridge->dev, &it6251->connector, + &it6251_connector_funcs, + DRM_MODE_CONNECTOR_eDP); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + drm_atomic_helper_connector_reset(&it6251->connector); + drm_connector_helper_add(&it6251->connector, + &it6251_connector_helper_funcs); + drm_mode_connector_attach_encoder(&it6251->connector, bridge->encoder); + + if (it6251->panel) + drm_panel_attach(it6251->panel, &it6251->connector); + + drm_helper_hpd_irq_event(it6251->connector.dev); + + return 0; +} + +static const struct drm_bridge_funcs it6251_bridge_funcs = { + .pre_enable = it6251_pre_enable, + .enable = it6251_enable, + .disable = it6251_disable, + .post_disable = it6251_post_disable, + .attach = it6251_attach, +}; + +static const struct regmap_config it6251_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .cache_type = REGCACHE_NONE, +}; + +static int +it6251_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct it6251_bridge *it6251; + struct device_node *endpoint, *panel_node; + int ret; + + it6251 = devm_kzalloc(dev, sizeof(*it6251), GFP_KERNEL); + if (!it6251) + return -ENOMEM; + + endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); + if (endpoint) { + panel_node = of_graph_get_remote_port_parent(endpoint); + if (panel_node) { + it6251->panel = of_drm_find_panel(panel_node); + of_node_put(panel_node); + if (!it6251->panel) + return -EPROBE_DEFER; + } + } + + it6251->client = client; + + it6251->regmap = devm_regmap_init_i2c(client, &it6251_regmap_config); + if (IS_ERR(it6251->regmap)) { + dev_err(dev, "cannot init i2c regmap for IT6251\n"); + return PTR_ERR(it6251->regmap); + } + + it6251->regulator = devm_regulator_get(dev, "power"); + if (IS_ERR(it6251->regulator)) { + dev_err(dev, "no power regulator found for IT6251\n"); + return PTR_ERR(it6251->regulator); + } + + /* The LVDS-half of the chip shows up at address 0x5e */ + it6251->lvds_client = i2c_new_secondary_device(client, "lvds", 0x5e); + if (!it6251->lvds_client) { + dev_err(dev, "cannot create I2C device for IT6251 LVDS\n"); + return -ENODEV; + } + + it6251->lvds_regmap = regmap_init_i2c(it6251->lvds_client, + &it6251_regmap_config); + if (IS_ERR(it6251->lvds_regmap)) { + dev_err(dev, "cannot init i2c regmap for IT6251 LVDS\n"); + goto err_lvds_regmap; + } + + i2c_set_clientdata(client, it6251); + + it6251->bridge.funcs = &it6251_bridge_funcs; + it6251->bridge.of_node = dev->of_node; + ret = drm_bridge_add(&it6251->bridge); + if (ret) { + DRM_ERROR("Failed to add bridge\n"); + goto err_bridge_add; + } + + return 0; + +err_bridge_add: + regmap_exit(it6251->lvds_regmap); +err_lvds_regmap: + i2c_unregister_device(it6251->lvds_client); + return ret; +} + +static int it6251_remove(struct i2c_client *client) +{ + struct it6251_bridge *it6251 = i2c_get_clientdata(client); + int ret; + + ret = it6251_power_down(it6251); + if (ret) + return ret; + + regmap_exit(it6251->lvds_regmap); + i2c_unregister_device(it6251->lvds_client); + + return 0; +} + +static int it6251_pm_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct it6251_bridge *it6251 = i2c_get_clientdata(client); + + return it6251_power_down(it6251); +} + +static int it6251_pm_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct it6251_bridge *it6251 = i2c_get_clientdata(client); + + return it6251_power_up(it6251); +} + +static const struct dev_pm_ops it6251_dev_pm_ops = { + .suspend = it6251_pm_suspend, + .resume = it6251_pm_resume, + .restore = it6251_pm_resume, +}; + +static struct i2c_device_id it6251_ids[] = { + { "it6251", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, it6251_ids); + +static const struct of_device_id it6251_of_match[] = { + { .compatible = "ite,it6251", }, + { } +}; +MODULE_DEVICE_TABLE(of, it6251_of_match); + +static struct i2c_driver it6251_driver = { + .driver = { + .name = "it6251", + .pm = &it6251_dev_pm_ops, + .of_match_table = it6251_of_match, + }, + .probe = it6251_probe, + .remove = it6251_remove, + .id_table = it6251_ids, +}; + +module_i2c_driver(it6251_driver); + +/* Module initialization */ +MODULE_AUTHOR("Sean Cross "); +MODULE_AUTHOR("Marek Vasut "); +MODULE_DESCRIPTION("ITE Tech 6251 LVDS to DisplayPort encoder"); +MODULE_LICENSE("GPL");