Received: by 2002:a25:868d:0:0:0:0:0 with SMTP id z13csp417885ybk; Sat, 9 May 2020 06:50:54 -0700 (PDT) X-Google-Smtp-Source: APiQypJDJev89Gzdq8nUVfQn8jANcUAmy0p2yaqW9MrUULuwWEMCyjvH2usweyJwSd8//GvPl6ro X-Received: by 2002:a17:906:6944:: with SMTP id c4mr6224128ejs.96.1589032254252; Sat, 09 May 2020 06:50:54 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1589032254; cv=none; d=google.com; s=arc-20160816; b=DTWVEnzJmzhf4upGcQ2djwxC8kExAnCAwWbNlq+aOcbjl4/FmoDv1hKlie/xhpeHtP ZE9dQL0Xbr7GhygLGCsdSlyyi5yw3FsR42fSYCMnm7BGEM/2n4O+SmLwmsT7ZfIgvGG0 XgqtGgI0XXwdCspPOpDtJno8sJAffLJ1rqsd3WrufRW3Ud8hGNydQYUUs6vO4Uj2ea5J POr0K3KfZmzH5HMnFUcxt7j8h2T48wMbM2W9C3Mu7efCUv9CAODYl16pDnTWzQ1rle7i EN1xa9DLWKObi34CVc5gIvCw2rTFWPU65GDH4tgshrz96LyOcIL8brh0VfQqPQyX+0dQ LwRQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:in-reply-to:content-disposition :mime-version:references:message-id:subject:cc:to:from:date :dkim-signature; bh=lU92BKuBmaFnkszd0Lxg4JzDXwGRFRqZWzX77Zf43mk=; b=imNGKIfGG2ESt/kSaYJo6PsIWS2NjWXHxUAkZd92i4MKZ76bE926b3ZttLR2YegeZz X+td6KHcA1UmUzXHrO6Tiw0ixZYYyxe+xWHkI8L5w8mwwKkMjXVbhtj97otywqcTvRa3 z7nxzBMjg6A+MoLTXTFCzZNhafOTQSXtp/AZuWtTwvOhc7dAthhAMdEvHjC3ERdrgN1q 66RqjJs5akS4/d3SppP77w9f3WX6UOmuQGSun/QQQWoV3gD90KHzLYwaj5TGkraHe9gk ufVHhtBnOkrVj3RMnXNQx9XUIcY4fzh+S6o0Dkx3qQvE07EXsL0nHLEVZevtdaHYAafZ k6mg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=hS7d4z7X; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id l21si2559465eds.265.2020.05.09.06.50.30; Sat, 09 May 2020 06:50:54 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=hS7d4z7X; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727812AbgEINtB (ORCPT + 99 others); Sat, 9 May 2020 09:49:01 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:39638 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726013AbgEINtB (ORCPT ); Sat, 9 May 2020 09:49:01 -0400 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id EF78AC061A0C; Sat, 9 May 2020 06:49:00 -0700 (PDT) Received: from pendragon.ideasonboard.com (81-175-216-236.bb.dnainternet.fi [81.175.216.236]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 964FC30D; Sat, 9 May 2020 15:48:56 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1589032136; bh=/3pPdXv0RBHJjn5kdXOokbD4b/fTlYfADjI4ccZsSfc=; h=Date:From:To:Cc:Subject:References:In-Reply-To:From; b=hS7d4z7XBUtby/ZSuDOLtQcV9hw/SFOe2JagHKsYytqCNfJPxKQtv/1plokopsHAT ua7+faAlZJwDodO93aSMYblx4ZVRT1SOJqKjpvPJC7LYGghVWRC6LOefrg45nK+zCs VkrgBnygM4pJKwjf1MCZ2tHBFpla7zUViSoIkAY4= Date: Sat, 9 May 2020 16:48:50 +0300 From: Laurent Pinchart To: srk@48.io Cc: Andrzej Hajda , Neil Armstrong , Jonas Karlman , Jernej Skrabec , David Airlie , Daniel Vetter , Rob Herring , Lubomir Rintel , dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Marek Vasut , Sean Cross Subject: Re: [PATCH 2/2] drm/bridge: Add ITE IT6251 bridge driver Message-ID: <20200509134850.GB5946@pendragon.ideasonboard.com> References: <20200509111732.26102-1-srk@48.io> <20200509111732.26102-3-srk@48.io> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Disposition: inline In-Reply-To: <20200509111732.26102-3-srk@48.io> Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Hi Richard, Thank you for the patch. On Sat, May 09, 2020 at 01:17:32PM +0200, srk@48.io wrote: > From: Marek Vasut > > Add driver for the ITE IT6251 LVDS-to-eDP bridge. > > Signed-off-by: Marek Vasut > Signed-off-by: Richard Marko > Cc: Daniel Vetter > Cc: Sean Cross > To: dri-devel@lists.freedesktop.org > --- > drivers/gpu/drm/bridge/Kconfig | 12 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/ite-it6251.c | 582 ++++++++++++++++++++++++++++ > 3 files changed, 595 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 aaed2347ace9..934896a4ab2d 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -38,6 +38,18 @@ config DRM_DISPLAY_CONNECTOR > on ARM-based platforms. Saying Y here when this driver is not needed > will not cause any issue. > > +config DRM_ITE_IT6251 > + tristate "ITE IT6251 LVDS/eDP bridge" > + depends on OF > + select DRM_KMS_HELPER > + select DRM_PANEL > + select REGMAP_I2C > + help > + Driver for ITE IT6251 LVDS-eDP bridge chip driver. This is used > + in Novena open-hardware laptop with eDP based panel. > + IT6251 supports LVDS input and DisplayPort 1.1a output, > + resolution up to 1080P and 10-bit color depth. > + > config DRM_LVDS_CODEC > tristate "Transparent LVDS encoders and decoders support" > depends on OF > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index 6fb062b5b0f0..4c195dc42fce 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -1,6 +1,7 @@ > # SPDX-License-Identifier: GPL-2.0 > obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o > obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o > +obj-$(CONFIG_DRM_ITE_IT6251) += ite-it6251.o > obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o > obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o > obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o > diff --git a/drivers/gpu/drm/bridge/ite-it6251.c b/drivers/gpu/drm/bridge/ite-it6251.c > new file mode 100644 > index 000000000000..b8534fb62c9d > --- /dev/null > +++ b/drivers/gpu/drm/bridge/ite-it6251.c > @@ -0,0 +1,582 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (C) 2014 Sean Cross > + * > + * Rework for mainline: Marek Vasut > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +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) > +{ > + unsigned int status, rpclkcnt, clkcnt, refstate, rpcreq; > + u16 hactive; > + u16 vactive; > + u8 regs[2]; > + 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; > + > + return 0; > +} > + > +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, connector); > +} > + > +static const struct drm_connector_helper_funcs it6251_connector_helper_funcs = { > + .get_modes = it6251_get_modes, > +}; > + > +static const struct drm_connector_funcs it6251_connector_funcs = { > + .fill_modes = drm_helper_probe_single_connector_modes, > + .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, enum drm_bridge_attach_flags flags) > +{ > + struct it6251_bridge *it6251 = bridge_to_it6251(bridge); > + int ret; > + New bridge drivers need to support the DRM_BRIDGE_ATTACH_NO_CONNECTOR flag, and implement the connector-related drm_bridge_funcs operations. See the "[PATCH v2 0/21] drm/bridge: support drm bridge connector helper + panel updates" patch series on the dri-devel mailing list for examples. > + 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_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_ancillary_device(client, "lvds", 0x5e); > + if (IS_ERR(it6251->lvds_client)) { > + dev_err(dev, "cannot create I2C device for IT6251 LVDS\n"); > + return PTR_ERR(it6251->lvds_client); > + } > + > + 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"); > + ret = PTR_ERR(it6251->lvds_regmap); > + goto err_lvds_regmap; > + } > + > + i2c_set_clientdata(client, it6251); > + > + it6251->bridge.funcs = &it6251_bridge_funcs; > + it6251->bridge.of_node = dev->of_node; > + drm_bridge_add(&it6251->bridge); > + > + return 0; > + > +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"); -- Regards, Laurent Pinchart