Received: by 2002:ac0:950c:0:0:0:0:0 with SMTP id f12csp3221033imc; Wed, 13 Mar 2019 11:59:11 -0700 (PDT) X-Google-Smtp-Source: APXvYqyLUoexzyPJkBBIDyywkRcH4gQeccZkV57Z7fUK9jV/Qnf0jrx0g9A399uuJaduJfAzF1w0 X-Received: by 2002:a65:4806:: with SMTP id h6mr41695821pgs.408.1552503551361; Wed, 13 Mar 2019 11:59:11 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1552503551; cv=none; d=google.com; s=arc-20160816; b=m1XG29p5jaWEpeU89GjTdwY5cqoj19A+3KU68UVJJhDH9jUpYCrKx1MZZ2/hAZ+U69 tFm6c4EcgCEWnYgmEcU/ZdXxGqBL8xn5MDDu+w0W2Jqc1ygGGa8gessEvQBkg6bAovIN rZ7xAdycVyTxikx8+jZEFFPevZfNEr9Kgz5PLlzsWJuNLnZjM61I9Yf9w2YdGC6ykklj BHScEW5smxIhkW/yhPzno2oSnlu4zwkHbNsvCYAD2FcrqeImKfF+3WtgALpyWG7HAuu/ Idk2TXmA3pM2wjOjaDQhlHfarNaSdjqq7+cuEXoY1u91sEz+vysnt6fkKhDwmCHOUsuB ASoQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:to:message-id:date:subject:cc:from :dkim-signature; bh=MqXBvHpRiaJgTS/t+O8DtCXEj6CRpZGdtN3oCG4O5zk=; b=O2ELx33kusRc2r5zxjDYz4kCBaNwpuXGMQOt1Mp7DT8pMCFH6eYPk6IibSAm0EuWVc XkjeMJEKxiXeoSUz7OIbLkBpd5PSusR2E5WBWMIrR5jrBtCNd4J7VKcFuhYyETIKY7NN 4jC905sOA3eQOwdseo8cNucuLThNDcMi6oMAtLt4tp6Q+MD0qYZIml3RIW3VqsYDR40p GeAJNS1Y+d/76Oh42dmt4+Q9Dqc8brvn+GA6Ka3WhXsuPjs2v0Hu0LlpyO4SXrxXRGUK SKFezLQTiJeAZqPaf54KlzhEDnKr5e2cE7U31Ci4maZp0am4bjKl+tF/esYix2QUOUGq NCkg== ARC-Authentication-Results: i=1; mx.google.com; dkim=fail header.i=@gmail.com header.s=20161025 header.b=p6Ofkfe1; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id g1si11190983plt.318.2019.03.13.11.58.54; Wed, 13 Mar 2019 11:59:11 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=fail header.i=@gmail.com header.s=20161025 header.b=p6Ofkfe1; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726656AbfCMS61 (ORCPT + 99 others); Wed, 13 Mar 2019 14:58:27 -0400 Received: from mail-yw1-f65.google.com ([209.85.161.65]:44194 "EHLO mail-yw1-f65.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726272AbfCMS61 (ORCPT ); Wed, 13 Mar 2019 14:58:27 -0400 Received: by mail-yw1-f65.google.com with SMTP id c4so2336840ywa.11 for ; Wed, 13 Mar 2019 11:58:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id; bh=MqXBvHpRiaJgTS/t+O8DtCXEj6CRpZGdtN3oCG4O5zk=; b=p6Ofkfe19Dc/ST+l8/ypePxvBEFAP6COyRkt8YEnS0Gh7gkVh9l2yPp+LVA2L4OHq1 tA1vTcGTMHqU69/w6Jhr6SkzaPih+wYY+hY0XdW21pEidfDWsG7zs4vNUB7Sw/dmfFoq sPaenuZmWqfg4VwJwg8yyiYxlLrDhhFLSRh1H7gkJ3wRQ1g92tfwWWJIHPkRhKUI/FWq Ic5ZOMoCCqe/zPvsvCIEN6w3hrpbJr9pgCEZlBIWK6JL07+ZWFYzfJbehw6X6FM3taJX 2SZhDeS5Ju3bb1aJjScIxphGYqn2fyH/3EWOelM+blwMIaxPJcwRiqP5X/ajxBoHRym5 TTpw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=MqXBvHpRiaJgTS/t+O8DtCXEj6CRpZGdtN3oCG4O5zk=; b=kLDWlQQmEuJF+9tajSkpXknW6px1MHmh6ejYVHPZxRTi+WEImVMJNSGKIYNxXjWnYm q7Y5v7xMouAo/9U80samux4xZQfCEb6lkBV65I6T8gvitGwdahtLhHnY/TelPr5Pcnqh Bl1RkmPwE58bFbU0wJ1UAz6kZfpJhd/1TiKaOd9PQluURK3nBadUraCQA8amSqNbT8dL gMOAiNjRndZSHcUoDCsls8hTzUl+uvv8K9PyHtAcjKa7eRlYHrbmiH3+p9biv/atSVRy f9qUU7JU+zuOlE2jf76zJAumX9DCAHostXy+9Mfy7irIzF4KuQJpcy6ID8nmdw5uy8p7 srqQ== X-Gm-Message-State: APjAAAVlyoqOsBtZCpWVMW034Ga9RjenDF8H8dHrl5q28EWjCnOmWWfs NgcKuBuXQkZggEUeot4lsIs= X-Received: by 2002:a0d:df52:: with SMTP id i79mr35900981ywe.448.1552503504876; Wed, 13 Mar 2019 11:58:24 -0700 (PDT) Received: from vince-laptop.corp.lucid.lcl ([12.151.33.163]) by smtp.googlemail.com with ESMTPSA id f131sm5731723ywf.93.2019.03.13.11.58.23 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 13 Mar 2019 11:58:24 -0700 (PDT) From: Vince Kim Cc: Vince Kim , Andrzej Hajda , Laurent Pinchart , David Airlie , Daniel Vetter , linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org Subject: [PATCH v2 1/2] drm/bridge: add initial support for TI FPDLINK bridge drivers Date: Wed, 13 Mar 2019 11:57:27 -0700 Message-Id: <20190313185812.32282-1-vince.k.kim@gmail.com> X-Mailer: git-send-email 2.17.1 To: unlisted-recipients:; (no To-header on input) Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org ti-fpdlink/ti-fpdlink.c: - A wrapper bridge driver for TI FPD LINK serializer and deserializer. - This driver takes care of initilizing FPD link connection between serializer and deserializer. - of_drm_find_bridge() looks up matching serializer/deserializer bridges from given serializer/deserializer device node. ti-fpdlink/ti-ub927.c: - drm bridge driver for TI DS90UB927 FPD-Link III Serializer ti-fpdlink/ti-ub949.c: - drm bridge driver for TI DS90UB949 FPD-Link III Serializer ti-fpdlink/ti-ub948.c: - drm bridge driver for TI DS90UB948 FPD-Link III Deserializer Signed-off-by: Vince Kim --- drivers/gpu/drm/bridge/Kconfig | 2 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ti-fpdlink/Kconfig | 32 ++ drivers/gpu/drm/bridge/ti-fpdlink/Makefile | 5 + .../gpu/drm/bridge/ti-fpdlink/ti-ds90ub927.c | 421 +++++++++++++++++ .../gpu/drm/bridge/ti-fpdlink/ti-ds90ub948.c | 333 ++++++++++++++ .../gpu/drm/bridge/ti-fpdlink/ti-ds90ub949.c | 435 ++++++++++++++++++ .../gpu/drm/bridge/ti-fpdlink/ti-fpdlink.c | 396 ++++++++++++++++ .../gpu/drm/bridge/ti-fpdlink/ti-fpdlink.h | 70 +++ 9 files changed, 1695 insertions(+) create mode 100644 drivers/gpu/drm/bridge/ti-fpdlink/Kconfig create mode 100644 drivers/gpu/drm/bridge/ti-fpdlink/Makefile create mode 100644 drivers/gpu/drm/bridge/ti-fpdlink/ti-ds90ub927.c create mode 100644 drivers/gpu/drm/bridge/ti-fpdlink/ti-ds90ub948.c create mode 100644 drivers/gpu/drm/bridge/ti-fpdlink/ti-ds90ub949.c create mode 100644 drivers/gpu/drm/bridge/ti-fpdlink/ti-fpdlink.c create mode 100644 drivers/gpu/drm/bridge/ti-fpdlink/ti-fpdlink.h diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 8840f396a7b6..f37a3c50dc69 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -154,4 +154,6 @@ source "drivers/gpu/drm/bridge/adv7511/Kconfig" source "drivers/gpu/drm/bridge/synopsys/Kconfig" +source "drivers/gpu/drm/bridge/ti-fpdlink/Kconfig" + endmenu diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 4934fcf5a6f8..6d251aba65d4 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -16,4 +16,5 @@ obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/ obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o +obj-$(CONFIG_DRM_TI_FPDLINK) += ti-fpdlink/ obj-y += synopsys/ diff --git a/drivers/gpu/drm/bridge/ti-fpdlink/Kconfig b/drivers/gpu/drm/bridge/ti-fpdlink/Kconfig new file mode 100644 index 000000000000..4e81e6454cfe --- /dev/null +++ b/drivers/gpu/drm/bridge/ti-fpdlink/Kconfig @@ -0,0 +1,32 @@ + +config DRM_TI_FPDLINK + tristate "TI FPD-Link III bridge" + depends on OF + select DRM_KMS_HELPER + help + Support wrapper bridge driver for Texas Instruments FPD-Link + Serializer and Deserializer chip. + +config DRM_TI_UB949 + tristate "TI FPD-Link III UB949 Serializer" + depends on DRM_TI_FPDLINK + select DRM_KMS_HELPER + help + Support Texas Instruments FPD-Link III DS90UB949 + Serializer which converts HDMI input to FPD-link output. + +config DRM_TI_UB948 + tristate "TI FPD-Link III UB948 Deserializer" + depends on DRM_TI_FPDLINK + select DRM_KMS_HELPER + help + Supports Texas Instruments FPD-Link III DS90UB948 + Deserializer which converts FPD-link input to LVDS output. + +config DRM_TI_UB927 + tristate "TI FPD-Link III UB927 Serializer" + depends on DRM_TI_FPDLINK + select DRM_KMS_HELPER + help + Support Texas Instruments FPD-Link III DS90UB927 + Serializer which converts LVDS input to FPD-link output. diff --git a/drivers/gpu/drm/bridge/ti-fpdlink/Makefile b/drivers/gpu/drm/bridge/ti-fpdlink/Makefile new file mode 100644 index 000000000000..a176a388f872 --- /dev/null +++ b/drivers/gpu/drm/bridge/ti-fpdlink/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_DRM_TI_FPDLINK) += ti-fpdlink.o +obj-$(CONFIG_DRM_TI_UB949) += ti-ds90ub949.o +obj-$(CONFIG_DRM_TI_UB948) += ti-ds90ub948.o +obj-$(CONFIG_DRM_TI_UB927) += ti-ds90ub927.o diff --git a/drivers/gpu/drm/bridge/ti-fpdlink/ti-ds90ub927.c b/drivers/gpu/drm/bridge/ti-fpdlink/ti-ds90ub927.c new file mode 100644 index 000000000000..88173f8b18ae --- /dev/null +++ b/drivers/gpu/drm/bridge/ti-fpdlink/ti-ds90ub927.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TI FPD-LinkIII DS90UB927 driver + * + * Copyright (C) 2019 Lucid Motors Inc. + * + * Contact: Vince Kim + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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 "ti-fpdlink.h" + +#define UB927_RESET_REG 0x01 +#define UB927_RESET_DGTL_RST1 (1<<1) + +#define UB927_GS_REG 0x0c +#define UB927_GS_BIST_CRC_ERROR (1<<3) +#define UB927_GS_PCLK_DETECT (1<<2) +#define UB927_GS_DES_CRC_ERROR (1<<1) +#define UB927_GS_LINK_DETECT (1<<0) + +#define UB927_I2C_CTRL_REG 0x17 +#define UB927_I2C_CTRL_PASS_ALL (1<<7) + +#define MAX_I2C_RETRY 10 + + +static inline struct fpdlink_dev * +drm_bridge_to_ub927(struct drm_bridge *bridge) +{ + return container_of(bridge, struct fpdlink_dev, bridge); +} + +static inline int +regmap_write_fpdlink(struct regmap *map, unsigned int reg, unsigned int val) +{ + int count = MAX_I2C_RETRY; + int ret = -1; + + while (count-- && (ret = regmap_write(map, reg, val))) + usleep_range(1000, 1100); + + return ret; +} + +static const struct regmap_config ub927_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_NONE, + .max_register = 0xff, +}; + +static int ub927_enable(struct fpdlink_dev *ub927) +{ + if (gpio_is_valid(ub927->gpio_power_en)) { + gpio_set_value(ub927->gpio_power_en, 1); + } else { + if (ub927->gpio_power_en > 0) { + dev_err(ub927->dev, "invalid gpio for gpio_disp_en %s\n", + ub927->gpio_name); + return -ENXIO; + } + } + msleep(200); + return 0; +} + +static void ub927_disable(struct fpdlink_dev *ub927) +{ + regmap_write_fpdlink(ub927->regmap, UB927_RESET_REG, + UB927_RESET_DGTL_RST1); +} + +static int ub927_reset(struct fpdlink_dev *ub927) +{ + int ret; + + ret = regmap_write_fpdlink(ub927->regmap, UB927_RESET_REG, + UB927_RESET_DGTL_RST1); + usleep_range(1000, 1500); + return ret; +} + +static int ub927_enable_i2c_passthrough(struct fpdlink_dev *ub927) +{ + int ret; + unsigned int val; + + ret = regmap_read(ub927->regmap, UB927_I2C_CTRL_REG, &val); + if (ret < 0) { + dev_err(ub927->dev, "failed on regmap_read at %s()\n", + __func__); + return ret; + } + + ret = regmap_write_fpdlink(ub927->regmap, UB927_I2C_CTRL_REG, + (val | UB927_I2C_CTRL_PASS_ALL)); + if (ret < 0) { + dev_err(ub927->dev, "failed onregmap_write_fpdlink at %s()\n", + __func__); + return ret; + } + + msleep(100); + return ret; +} + +static int ub927_reg_write(struct fpdlink_dev *ub927, u8 reg, u8 val) +{ + return regmap_write_fpdlink(ub927->regmap, reg, val); +} + +static int ub927_update_config(struct fpdlink_dev *ub927) +{ + int i = 0; + int size; + u8 *config_array; + int ret = 0; + + size = ub927->config_array_size; + config_array = ub927->config_array; + + if (!config_array) + return 0; + + while (i < size) { + ret = regmap_write_fpdlink(ub927->regmap, config_array[i], + config_array[i+1]); + if (ret) { + dev_err(ub927->dev, + "%s: failed to update register\n", __func__); + return -EINVAL; + } + i += 2; + } + return 0; +} + +static bool ub927_link_detect(struct fpdlink_dev *ub927) +{ + unsigned int val; + int count, ret; + + mutex_lock(&ub927->lock); + ub927->link_status = not_detected; + + for (count = 0; count < MAX_I2C_RETRY; count++) { + usleep_range(10000, 15000); + ret = regmap_read(ub927->regmap, UB927_GS_REG, &val); + if (!ret && (val & UB927_GS_LINK_DETECT)) { + dev_info(ub927->dev, + "LVDS: %d Serializer FPD link ready!\n", + ub927->index); + ub927->link_status = detected; + ret = true; + goto done; + } + } + + dev_info(ub927->dev, "LVDS: %d Serializer FPD link failed!!\n", + ub927->index); + +done: + mutex_unlock(&ub927->lock); + + if (ret < 0) { + dev_info(ub927->dev, + "failed on regmap_read at %s()\n", __func__); + } + return (ub927->link_status ? true : false); +} + +static bool ub927_pixel_clk_detect(struct fpdlink_dev *ub927) +{ + unsigned int val; + int count, ret; + + mutex_lock(&ub927->lock); + + for (count = 0; count < MAX_I2C_RETRY; count++) { + ret = regmap_read(ub927->regmap, UB927_GS_REG, &val); + if (!ret && (val & UB927_GS_PCLK_DETECT)) { + dev_info(ub927->dev, + "LVDS: %d, Serializer LVDS CLK OK!!\n", + ub927->index); + ub927->pixel_clk_status = detected; + ret = true; + goto done; + } + } + dev_info(ub927->dev, + "LVDS:%d LVDS CLK is not detected at Serializer !!\n", + ub927->index); + ub927->pixel_clk_status = not_detected; + +done: + mutex_unlock(&ub927->lock); + if (ret < 0) { + dev_info(ub927->dev, + "failed on regmap_read at %s()\n", __func__); + } + return (ub927->pixel_clk_status ? true : false); + +} + +static int parse_config_val(struct fpdlink_dev *ub927, struct device_node *np) +{ + const u32 *config_property; + int len; + u8 *config_array; + int config_array_size; + int i = 0; + + config_property = of_get_property(np, "reg_config", &len); + config_array_size = len/sizeof(u32); + + /*config value must be pair of register offset and value */ + if (!config_property || config_array_size <= 1 || config_array_size%2) + return -EINVAL; + + config_array = kcalloc(config_array_size, sizeof(u8), GFP_KERNEL); + if (!config_array_size) + return -ENOMEM; + + for (i = 0; i < config_array_size; i++) + config_array[i] = (u8)be32_to_cpu(config_property[i]); + + ub927->config_array_size = config_array_size; + ub927->config_array = config_array; + return 0; +} + +static const struct fpdlink_dev_funcs ub927_fpdlink_dev_funcs = { + .enable = ub927_enable, + .reset = ub927_reset, + .enable_i2c_passthrough = ub927_enable_i2c_passthrough, + .reg_write = ub927_reg_write, + .link_detect = ub927_link_detect, + .pixel_clk_detect = ub927_pixel_clk_detect, + .config = ub927_update_config, +}; + +static void ub927_shutdown(struct i2c_client *client) +{ + struct fpdlink_dev *ub927 = i2c_get_clientdata(client); + + ub927_disable(ub927); +} + +static ssize_t link_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpdlink_dev *ub927 = dev_get_drvdata(dev); + int ret = 0; + int count, status; + unsigned int val; + + ret = mutex_lock_interruptible(&ub927->lock); + if (ret) + return ret; + + ub927->link_status = not_detected; + for (count = 0; count < MAX_I2C_RETRY; count++) { + usleep_range(10000, 15000); + ret = regmap_read(ub927->regmap, UB927_GS_REG, &val); + if (!ret && (val & UB927_GS_LINK_DETECT)) { + ub927->link_status = detected; + break; + } + usleep_range(10000, 15000); + } + status = ub927->link_status; + mutex_unlock(&ub927->lock); + + if (ret < 0) { + dev_info(ub927->dev, "failed on regmap_read at %s()\n", + __func__); + } + + return snprintf(buf, PAGE_SIZE, "%s\n", + status ? "connected":"disconnected"); +} + +static ssize_t pixel_clk_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpdlink_dev *ub927 = dev_get_drvdata(dev); + int count, status; + int ret = 0; + unsigned int val; + + ret = mutex_lock_interruptible(&ub927->lock); + if (ret) + return ret; + + ub927->pixel_clk_status = not_detected; + for (count = 0; count < MAX_I2C_RETRY; count++) { + ret = regmap_read(ub927->regmap, UB927_GS_REG, &val); + if (!ret && (val & UB927_GS_PCLK_DETECT)) { + ub927->pixel_clk_status = detected; + break; + } + usleep_range(10000, 15000); + } + + status = ub927->pixel_clk_status; + mutex_unlock(&ub927->lock); + + if (ret < 0) { + dev_info(ub927->dev, "failed on regmap_read at %s()\n", + __func__); + } + return snprintf(buf, PAGE_SIZE, "%s\n", status ? "on":"off"); +} + +static DEVICE_ATTR_RO(link_status); +static DEVICE_ATTR_RO(pixel_clk_status); + + +static int ub927_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct fpdlink_dev *ub927; + struct device_node *node_ptr; + + int ret = 0; + + ub927 = devm_kzalloc(dev, sizeof(*ub927), GFP_KERNEL); + if (!ub927) + return -ENOMEM; + + ub927->dev = dev; + node_ptr = dev->of_node; + + ub927->gpio_power_en = of_get_named_gpio(node_ptr, "power-en-pin", 0); + if (ub927->gpio_power_en > 0) { + sprintf(ub927->gpio_name, "ub927 %d", ub927->index); + if (!gpio_is_valid(ub927->gpio_power_en)) { + dev_err(dev, "invalid gpio for gpio_power_en %s\n", + ub927->gpio_name); + ret = -ENXIO; + goto done; + } + } + + ub927->client = client; + ub927->regmap = devm_regmap_init_i2c(client, &ub927_regmap_config); + if (IS_ERR(ub927->regmap)) { + dev_err(dev, "failed to allocate register map\n"); + ret = PTR_ERR(ub927->regmap); + goto done; + } + + ub927->bridge.of_node = node_ptr; + ub927_disable(ub927); + + mutex_init(&ub927->lock); + ub927->funcs = &ub927_fpdlink_dev_funcs; + parse_config_val(ub927, node_ptr); + i2c_set_clientdata(client, ub927); + + drm_bridge_add(&ub927->bridge); + + device_create_file(dev, &dev_attr_link_status); + device_create_file(dev, &dev_attr_pixel_clk_status); + +done: + if (ret < 0) + devm_kfree(&client->dev, ub927); + return ret; +} + +static int ub927_remove(struct i2c_client *client) +{ + struct fpdlink_dev *ub927 = i2c_get_clientdata(client); + + drm_bridge_remove(&ub927->bridge); + return 0; +} + +static const struct i2c_device_id ub927_id[] = { + { "ds90ub927", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ub927_id); + +static const struct of_device_id ub927_match[] = { + { .compatible = "ti,ds90ub927" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ub927_match); + +static struct i2c_driver ti_ub927_driver = { + .id_table = ub927_id, + .probe = ub927_probe, + .remove = ub927_remove, + .shutdown = ub927_shutdown, + .driver = { + .name = "ti-ub927", + .of_match_table = ub927_match, + }, +}; +module_i2c_driver(ti_ub927_driver); + +MODULE_AUTHOR("Vince Kim "); +MODULE_DESCRIPTION("TI UB927 Serializer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/ti-fpdlink/ti-ds90ub948.c b/drivers/gpu/drm/bridge/ti-fpdlink/ti-ds90ub948.c new file mode 100644 index 000000000000..f42130a19533 --- /dev/null +++ b/drivers/gpu/drm/bridge/ti-fpdlink/ti-ds90ub948.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TI FPD-LinkIII DS90UB948 driver + * + * Copyright (C) 2019 Lucid Motors Inc. + * + * Contact: Vince Kim + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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 "ti-fpdlink.h" + +#define UB948_RESET_REG 0x01 +#define UB948_RESET_DGTL_RST0 (1<<2) + +#define UB948_GS_REG 0x1c +#define UB948_GS_LOCK_DETECT (1<<0) + +#define UB948_DUAL_CTL1_REG 0x5b + +#define UB948_DUAL_STS_REG 0x5a +#define UB948_DUAL_STS_FPD3_LINK_READY (1<<7) + +#define MAX_I2C_RETRY 10 + +static inline struct fpdlink_dev * +drm_bridge_to_ub948(struct drm_bridge *bridge) +{ + return container_of(bridge, struct fpdlink_dev, bridge); +} + +static inline int regmap_write_fpdlink(struct regmap *map, unsigned int reg, + unsigned int val) +{ + int count = MAX_I2C_RETRY; + int ret = -1; + + while (count-- && (ret = regmap_write(map, reg, val))) + usleep_range(1000, 1100); + + return ret; +} + +static const struct regmap_config ub948_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_NONE, + .max_register = 0xff, +}; + +static int ub948_enable(struct fpdlink_dev *ub948) +{ + if (gpio_is_valid(ub948->gpio_power_en)) { + gpio_set_value(ub948->gpio_power_en, 1); + } else { + if (ub948->gpio_power_en > 0) { + dev_err(ub948->dev, + "invalid gpio for gpio_disp_en %s\n", + ub948->gpio_name); + return -ENXIO; + } + } + msleep(200); + return 0; +} + +static void ub948_disable(struct fpdlink_dev *ub948) +{ + if (gpio_is_valid(ub948->gpio_power_en)) + gpio_set_value(ub948->gpio_power_en, 0); +} + +static int ub948_reset(struct fpdlink_dev *ub948) +{ + int ret; + + ret = regmap_write_fpdlink(ub948->regmap, + UB948_RESET_REG, UB948_RESET_DGTL_RST0); + msleep(100); + if (ret == 0) + ub948->detected = true; + return ret; +} + +static int ub948_reg_write(struct fpdlink_dev *ub948, u8 reg, u8 val) +{ + return regmap_write_fpdlink(ub948->regmap, reg, val); +} + +static int ub948_update_config(struct fpdlink_dev *ub948) +{ + int i = 0; + int size; + u8 *config_array; + int ret = 0; + + size = ub948->config_array_size; + config_array = ub948->config_array; + + if (!config_array) + return 0; + + while (i < size) { + ret = regmap_write_fpdlink(ub948->regmap, config_array[i], + config_array[i+1]); + if (ret) { + dev_err(ub948->dev, + "%s: failed to update register\n", __func__); + return -EINVAL; + } + i += 2; + } + return 0; +} + +static bool ub948_link_detect(struct fpdlink_dev *ub948) +{ + unsigned int val; + int count, ret; + + if (ub948->detected == false) + return false; + + mutex_lock(&ub948->lock); + ub948->link_status = not_detected; + for (count = 0; count < MAX_I2C_RETRY; count++) { + ret = regmap_read(ub948->regmap, UB948_GS_REG, &val); + + if (!ret && (val & UB948_GS_LOCK_DETECT)) { + dev_info(ub948->dev, + "HDMI: %d DeSerializer FPD link ready!\n", + ub948->index); + ub948->link_status = detected; + ret = true; + goto done; + } + usleep_range(10000, 11000); + } + dev_info(ub948->dev, "HDMI: %d DeSerializer FPD link failed!!\n", + ub948->index); + +done: + mutex_unlock(&ub948->lock); + + if (ret < 0) + dev_info(ub948->dev, "failed on regmap_read at %s()\n", + __func__); + + return ub948->link_status ? true : false; +} + +static int parse_config_val(struct fpdlink_dev *ub948, + struct device_node *node_ptr) +{ + const u32 *config_property; + int len; + u8 *config_array; + int config_array_size; + int i = 0; + + config_property = of_get_property(node_ptr, "reg_config", &len); + config_array_size = len/sizeof(u32); + + /*config value must be pair of register offset and value */ + if (!config_property || config_array_size <= 1 || config_array_size%2) + return -EINVAL; + + config_array = kcalloc(config_array_size, sizeof(u8), GFP_KERNEL); + if (!config_array_size) + return -ENOMEM; + + for (i = 0; i < config_array_size; i++) + config_array[i] = (u8)be32_to_cpu(config_property[i]); + + ub948->config_array_size = config_array_size; + ub948->config_array = config_array; + return 0; +} + +static const struct fpdlink_dev_funcs ub948_fpdlink_dev_funcs = { + .enable = ub948_enable, + .reset = ub948_reset, + .reg_write = ub948_reg_write, + .link_detect = ub948_link_detect, + .config = ub948_update_config, +}; + +static void ub948_shutdown(struct i2c_client *client) +{ + struct fpdlink_dev *ub948 = i2c_get_clientdata(client); + + ub948_disable(ub948); +} + +static ssize_t lock_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpdlink_dev *ub948 = dev_get_drvdata(dev); + int ret = 0; + int status; + unsigned int val; + int count; + + if (ub948->detected == false) + return snprintf(buf, PAGE_SIZE, "%s\n", "off"); + + ret = mutex_lock_interruptible(&ub948->lock); + if (ret) + return ret; + + ub948->link_status = not_detected; + for (count = 0; count < MAX_I2C_RETRY; count++) { + ret = regmap_read(ub948->regmap, UB948_GS_REG, &val); + if (!ret && (val & UB948_GS_LOCK_DETECT)) { + ub948->link_status = detected; + break; + } + usleep_range(10000, 11000); + } + status = ub948->link_status; + mutex_unlock(&ub948->lock); + + if (ret < 0) { + dev_info(ub948->dev, "failed on regmap_read at %s()\n", + __func__); + } + + return snprintf(buf, PAGE_SIZE, "%s\n", status ? "on":"off"); +} +static DEVICE_ATTR_RO(lock_status); + +static int ub948_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct fpdlink_dev *ub948; + struct device_node *node_ptr; + + int ret = 0; + + ub948 = devm_kzalloc(dev, sizeof(*ub948), GFP_KERNEL); + if (!ub948) + return -ENOMEM; + + ub948->dev = dev; + node_ptr = dev->of_node; + + ub948->gpio_power_en = of_get_named_gpio(node_ptr, "power-en-pin", 0); + if (ub948->gpio_power_en > 0) { + sprintf(ub948->gpio_name, "ub948 %d", ub948->index); + if (!gpio_is_valid(ub948->gpio_power_en)) { + dev_err(dev, "invalid gpio for gpio_power_en %s\n", + ub948->gpio_name); + ret = -ENXIO; + goto done; + } + } + + ub948->client = client; + ub948->regmap = devm_regmap_init_i2c(client, &ub948_regmap_config); + if (IS_ERR(ub948->regmap)) { + dev_err(dev, "failed to allocate register map\n"); + ret = PTR_ERR(ub948->regmap); + goto done; + } + + mutex_init(&ub948->lock); + ub948->bridge.of_node = node_ptr; + ub948_disable(ub948); + + ub948->funcs = &ub948_fpdlink_dev_funcs; + parse_config_val(ub948, node_ptr); + i2c_set_clientdata(client, ub948); + + drm_bridge_add(&ub948->bridge); + device_create_file(dev, &dev_attr_lock_status); + /* + * we don't know if deserializer is attached until serializer is ready. + */ + ub948->detected = false; + +done: + return ret; +} + +static int ub948_remove(struct i2c_client *client) +{ + struct fpdlink_dev *ub948 = i2c_get_clientdata(client); + + drm_bridge_remove(&ub948->bridge); + return 0; +} + +static const struct i2c_device_id ub948_id[] = { + { "ds90ub948", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ub948_id); + +static const struct of_device_id ub948_match[] = { + { .compatible = "ti,ds90ub948" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ub948_match); + +static struct i2c_driver ti_ub948_driver = { + .id_table = ub948_id, + .probe = ub948_probe, + .remove = ub948_remove, + .shutdown = ub948_shutdown, + .driver = { + .name = "ti-ub948", + .of_match_table = ub948_match, + }, +}; +module_i2c_driver(ti_ub948_driver); + +MODULE_AUTHOR("Vince Kim "); +MODULE_DESCRIPTION("TI UB948 Serializer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/ti-fpdlink/ti-ds90ub949.c b/drivers/gpu/drm/bridge/ti-fpdlink/ti-ds90ub949.c new file mode 100644 index 000000000000..bb99d296fbb8 --- /dev/null +++ b/drivers/gpu/drm/bridge/ti-fpdlink/ti-ds90ub949.c @@ -0,0 +1,435 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TI FPD-LinkIII DS90UB949 driver + * + * Copyright (C) 2019 Lucid Motors Inc. + * + * Contact: Vince Kim + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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 "ti-fpdlink.h" + +#define UB949_RESET_REG 0x01 +#define UB949_RESET_HDMI_RST (1<<4) +#define UB949_RESET_DGTL_RST1 (1<<1) + +#define UB949_GS_REG 0x0c +#define UB949_GS_BIST_CRC_ERROR (1<<3) +#define UB949_GS_TMDS_CLK_DETECT (1<<2) +#define UB949_GS_DES_CRC_ERROR (1<<1) +#define UB949_GS_LINK_DETECT (1<<0) + +#define UB949_I2C_CTRL_REG 0x17 +#define UB949_I2C_CTRL_PASS_ALL (1<<7) + +#define UB949_DUAL_STS_REG 0x5a +#define UB949_DUAL_STS_FPD3_LINK_READY (1<<7) +#define UB949_DUAL_STS_FPD3_TX_STS (1<<6) + +#define UB949_DUAL_CTL1_REG 0x5b +#define UB949_DUAL_CTL2_REG 0x5c +#define UB949_HDMI_FREQ 0x5f + +#define MAX_I2C_RETRY 10 + + +static inline struct fpdlink_dev * +drm_bridge_to_ub949(struct drm_bridge *bridge) +{ + return container_of(bridge, struct fpdlink_dev, bridge); +} + +static inline int regmap_write_fpdlink(struct regmap *map, unsigned int reg, + unsigned int val) +{ + int count = MAX_I2C_RETRY; + int ret = -1; + + while (count-- && (ret = regmap_write(map, reg, val))) + usleep_range(1000, 1100); + + return ret; +} + +static const struct regmap_config ub949_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_NONE, + .max_register = 0xff, +}; + +static int ub949_enable(struct fpdlink_dev *ub949) +{ + if (gpio_is_valid(ub949->gpio_power_en)) { + gpio_set_value(ub949->gpio_power_en, 1); + } else { + if (ub949->gpio_power_en > 0) { + dev_err(ub949->dev, + "invalid gpio for gpio_disp_en %s\n", + ub949->gpio_name); + return -ENXIO; + } + } + + msleep(200); + return 0; +} + +static void ub949_disable(struct fpdlink_dev *ub949) +{ + regmap_write_fpdlink(ub949->regmap, UB949_RESET_REG, + (UB949_RESET_HDMI_RST|UB949_RESET_DGTL_RST1)); +} + +static int ub949_reset(struct fpdlink_dev *ub949) +{ + int ret; + + ret = regmap_write_fpdlink(ub949->regmap, UB949_RESET_REG, + (UB949_RESET_HDMI_RST|UB949_RESET_DGTL_RST1)); + usleep_range(10000, 11000); + + return ret; +} + +static int ub949_enable_i2c_passthrough(struct fpdlink_dev *ub949) +{ + int ret; + unsigned int val; + + ret = regmap_read(ub949->regmap, UB949_I2C_CTRL_REG, &val); + if (ret < 0) { + dev_err(ub949->dev, "failed on regmap_read at %s()\n", + __func__); + return ret; + } + + ret = regmap_write_fpdlink(ub949->regmap, UB949_I2C_CTRL_REG, + (val | UB949_I2C_CTRL_PASS_ALL)); + msleep(100); + + return ret; +} + +static int ub949_reg_write(struct fpdlink_dev *ub949, u8 reg, u8 val) +{ + return regmap_write_fpdlink(ub949->regmap, reg, val); +} + +static int ub949_update_config(struct fpdlink_dev *ub949) +{ + int i = 0; + int size; + u8 *config_array; + int ret = 0; + + size = ub949->config_array_size; + config_array = ub949->config_array; + + if (!config_array) + return 0; + + while (i < size) { + ret = regmap_write_fpdlink(ub949->regmap, config_array[i], + config_array[i+1]); + if (ret) { + dev_err(ub949->dev, + "%s: failed to update register\n", __func__); + return -EINVAL; + } + i += 2; + } + + return 0; +} + +static bool ub949_link_detect(struct fpdlink_dev *ub949) +{ + unsigned int val, val2; + int count, ret; + + mutex_lock(&ub949->lock); + ub949->link_status = not_detected; + + for (count = 0; count < MAX_I2C_RETRY; count++) { + usleep_range(10000, 11000); + ret = regmap_read(ub949->regmap, UB949_GS_REG, &val); + ret += regmap_read(ub949->regmap, UB949_DUAL_STS_REG, &val2); + if (!ret && (val & UB949_GS_LINK_DETECT) && + (val2 & UB949_DUAL_STS_FPD3_LINK_READY)) { + dev_info(ub949->dev, + "HDMI: %d Serializer FPD link ready!\n", + ub949->index); + ub949->link_status = detected; + ret = true; + goto done; + } + } + dev_info(ub949->dev, + "HDMI:%d Serializer FPD link failed!!\n", ub949->index); + +done: + mutex_unlock(&ub949->lock); + + if (ret < 0) { + dev_info(ub949->dev, + "failed on regmap_read at %s()\n", __func__); + } + + return (ub949->link_status ? true : false); +} + +static bool ub949_pixel_clk_detect(struct fpdlink_dev *ub949) +{ + unsigned int val; + int count, ret; + + mutex_lock(&ub949->lock); + + for (count = 0; count < 1; count++) { + ret = regmap_read(ub949->regmap, UB949_HDMI_FREQ, &val); + if (!ret && val) { + dev_info(ub949->dev, + "HDMI: %d, Serializer HDMI CLK OK!!\n", + ub949->index); + ub949->pixel_clk_status = detected; + ret = true; + goto done; + } + } + dev_info(ub949->dev, + "HDMI:%d HDMI CLK is not detected at Serializer !!\n", + ub949->index); + ub949->pixel_clk_status = not_detected; + +done: + mutex_unlock(&ub949->lock); + + if (ret < 0) + dev_info(ub949->dev, + "failed on regmap_read at %s()\n", __func__); + + return ub949->pixel_clk_status ? true : false; + +} + +static int parse_config_val(struct fpdlink_dev *ub949, + struct device_node *node_ptr) +{ + const u32 *config_property; + int len; + u8 *config_array; + int config_array_size; + int i = 0; + + config_property = of_get_property(node_ptr, "reg_config", &len); + config_array_size = len/sizeof(u32); + + /*config value must be a pair of register offset and value */ + if (!config_property || config_array_size <= 1 || config_array_size%2) + return -EINVAL; + + config_array = kcalloc(config_array_size, sizeof(u8), GFP_KERNEL); + if (!config_array_size) + return -ENOMEM; + + for (i = 0; i < config_array_size; i++) + config_array[i] = (u8)be32_to_cpu(config_property[i]); + + ub949->config_array_size = config_array_size; + ub949->config_array = config_array; + return 0; +} + +static const struct fpdlink_dev_funcs ub949_fpdlink_dev_funcs = { + .enable = ub949_enable, + .reset = ub949_reset, + .enable_i2c_passthrough = ub949_enable_i2c_passthrough, + .reg_write = ub949_reg_write, + .link_detect = ub949_link_detect, + .pixel_clk_detect = ub949_pixel_clk_detect, + .config = ub949_update_config, +}; + +static void ub949_shutdown(struct i2c_client *client) +{ + struct fpdlink_dev *ub949 = i2c_get_clientdata(client); + + ub949_disable(ub949); +} + +static ssize_t link_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpdlink_dev *ub949 = dev_get_drvdata(dev); + int status, ret; + unsigned int val, val2; + int count; + + ret = mutex_lock_interruptible(&ub949->lock); + if (ret) + return ret; + + ub949->link_status = not_detected; + + for (count = 0; count < MAX_I2C_RETRY; count++) { + ret = regmap_read(ub949->regmap, UB949_GS_REG, &val); + ret += regmap_read(ub949->regmap, UB949_DUAL_STS_REG, &val2); + if (!ret && (val & UB949_GS_LINK_DETECT) + && (val2 & UB949_DUAL_STS_FPD3_LINK_READY)) { + ub949->link_status = detected; + break; + } + usleep_range(10000, 11000); + } + status = ub949->link_status; + mutex_unlock(&ub949->lock); + + if (ret < 0) + dev_err(ub949->dev, + "failed on regmap_read at %s()\n", __func__); + + return snprintf(buf, PAGE_SIZE, "%s\n", + status ? "connected":"disconnected"); +} + +static ssize_t pixel_clk_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpdlink_dev *ub949 = dev_get_drvdata(dev); + int status, ret; + unsigned int val, val2; + int count; + + ret = mutex_lock_interruptible(&ub949->lock); + if (ret) + return ret; + + ub949->pixel_clk_status = not_detected; + for (count = 0; count < MAX_I2C_RETRY; count++) { + ret = regmap_read(ub949->regmap, UB949_HDMI_FREQ, &val); + ret += regmap_read(ub949->regmap, UB949_GS_REG, &val2); + if (!ret && val && (val2 & UB949_GS_TMDS_CLK_DETECT)) { + ub949->pixel_clk_status = detected; + break; + } + usleep_range(10000, 11000); + } + status = ub949->pixel_clk_status; + mutex_unlock(&ub949->lock); + + if (ret < 0) + dev_err(ub949->dev, + "failed on regmap_read at %s()\n", __func__); + + return snprintf(buf, PAGE_SIZE, "%s\n", status ? "on":"off"); +} + +static DEVICE_ATTR_RO(link_status); +static DEVICE_ATTR_RO(pixel_clk_status); + +static int ub949_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct fpdlink_dev *ub949; + struct device_node *node_ptr; + + int ret = 0; + + ub949 = devm_kzalloc(dev, sizeof(*ub949), GFP_KERNEL); + if (!ub949) + return -ENOMEM; + + ub949->dev = dev; + node_ptr = dev->of_node; + + ub949->gpio_power_en = of_get_named_gpio(node_ptr, "power-en-pin", 0); + if (ub949->gpio_power_en > 0) { + sprintf(ub949->gpio_name, "ub949 %d", ub949->index); + if (!gpio_is_valid(ub949->gpio_power_en)) { + dev_err(dev, "invalid gpio for gpio_power_en %s\n", + ub949->gpio_name); + ret = -ENXIO; + goto done; + } + } + + ub949->client = client; + ub949->regmap = devm_regmap_init_i2c(client, &ub949_regmap_config); + if (IS_ERR(ub949->regmap)) { + dev_err(dev, "failed to allocate register map\n"); + ret = PTR_ERR(ub949->regmap); + goto done; + } + + mutex_init(&ub949->lock); + + ub949->bridge.of_node = node_ptr; + ub949_disable(ub949); + + ub949->funcs = &ub949_fpdlink_dev_funcs; + parse_config_val(ub949, node_ptr); + i2c_set_clientdata(client, ub949); + + drm_bridge_add(&ub949->bridge); + + device_create_file(dev, &dev_attr_link_status); + device_create_file(dev, &dev_attr_pixel_clk_status); + +done: + if (ret < 0) + devm_kfree(&client->dev, ub949); + + return ret; +} + +static int ub949_remove(struct i2c_client *client) +{ + struct fpdlink_dev *ub949 = i2c_get_clientdata(client); + + drm_bridge_remove(&ub949->bridge); + return 0; +} + +static const struct i2c_device_id ub949_id[] = { + { "ds90ub949", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ub949_id); + +static const struct of_device_id ub949_match[] = { + { .compatible = "ti,ds90ub949" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ub949_match); + +static struct i2c_driver ti_ub949_driver = { + .id_table = ub949_id, + .probe = ub949_probe, + .remove = ub949_remove, + .shutdown = ub949_shutdown, + .driver = { + .name = "ti-ub949", + .of_match_table = ub949_match, + }, +}; +module_i2c_driver(ti_ub949_driver); + +MODULE_AUTHOR("Vince Kim "); +MODULE_DESCRIPTION("TI UB949 Serializer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/ti-fpdlink/ti-fpdlink.c b/drivers/gpu/drm/bridge/ti-fpdlink/ti-fpdlink.c new file mode 100644 index 000000000000..fa4cd5806f19 --- /dev/null +++ b/drivers/gpu/drm/bridge/ti-fpdlink/ti-fpdlink.c @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TI FPD-LinkIII interface bridge driver + * + * Copyright (C) 2019 Lucid Motors Inc. + * + * Contact: Vince Kim + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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 "ti-fpdlink.h" + +struct fpdlink { + struct drm_bridge bridge; + struct drm_connector connector; + + struct fpdlink_dev *serializer; + struct fpdlink_dev *deserializer; + struct device_node *fpdlink_node; + struct mutex lock; + + unsigned int index; + enum clk_status pixel_clk_status; + enum clk_status link_status; + bool enabled; +}; + + +static inline struct fpdlink * +drm_bridge_to_fpdlink(struct drm_bridge *bridge) +{ + return container_of(bridge, struct fpdlink, bridge); +} + +static inline struct fpdlink * +drm_connector_to_fpdlink(struct drm_connector *connector) +{ + return container_of(connector, struct fpdlink, connector); +} + +static int fpdlink_dev_enable(struct fpdlink_dev *dev) +{ + int ret; + + if (!dev) + return -EINVAL; + if (!dev->funcs) + return -EINVAL; + if (!dev->funcs->enable) + return -EINVAL; + + ret = dev->funcs->enable(dev); + + return ret; +} + +static int fpdlink_dev_reset(struct fpdlink_dev *dev) +{ + int ret; + + if (!dev) + return -EINVAL; + if (!dev->funcs) + return -EINVAL; + if (!dev->funcs->reset) + return -EINVAL; + + ret = dev->funcs->reset(dev); + return ret; +} + +static int fpdlink_dev_enable_i2c_passthrough(struct fpdlink_dev *dev) +{ + int ret; + + if (!dev) + return -EINVAL; + if (!dev->funcs) + return -EINVAL; + if (!dev->funcs->enable_i2c_passthrough) + return -EINVAL; + + ret = dev->funcs->enable_i2c_passthrough(dev); + return ret; +} + +static bool fpdlink_dev_link_detect(struct fpdlink_dev *dev) +{ + bool ret; + + if (!dev) + return -EINVAL; + if (!dev->funcs) + return -EINVAL; + if (!dev->funcs->link_detect) + return -EINVAL; + + ret = dev->funcs->link_detect(dev); + return ret; +} + +static bool fpdlink_dev_pixel_clk_detect(struct fpdlink_dev *dev) +{ + bool ret; + + if (!dev) + return -EINVAL; + if (!dev->funcs) + return -EINVAL; + if (!dev->funcs->pixel_clk_detect) + return -EINVAL; + + ret = dev->funcs->pixel_clk_detect(dev); + return ret; +} + +static int fpdlink_dev_config(struct fpdlink_dev *dev) +{ + int ret; + + if (!dev) + return -EINVAL; + if (!dev->funcs) + return -EINVAL; + if (!dev->funcs->config) + return -EINVAL; + + ret = dev->funcs->config(dev); + return ret; +} + +static enum drm_connector_status +fpdlink_connector_detect(struct drm_connector *connector, bool force) +{ + struct fpdlink *fpdlink_bridge = drm_connector_to_fpdlink(connector); + + if (fpdlink_bridge->link_status == detected) + return connector_status_connected; + + return connector_status_disconnected; +} + +static const struct drm_connector_funcs fpdlink_con_funcs = { + .detect = fpdlink_connector_detect, + .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 void fpdlink_pre_enable(struct drm_bridge *bridge) +{ + struct fpdlink *fpdlink_bridge = drm_bridge_to_fpdlink(bridge); + + struct fpdlink_dev *serializer = fpdlink_bridge->serializer; + struct fpdlink_dev *deserializer = fpdlink_bridge->deserializer; + int ret = 0; + + mutex_lock(&fpdlink_bridge->lock); + + if (fpdlink_bridge->link_status == detected) + goto done; + + fpdlink_dev_enable(deserializer); + fpdlink_dev_enable(serializer); + fpdlink_dev_reset(serializer); + fpdlink_dev_enable_i2c_passthrough(serializer); + ret = fpdlink_dev_reset(deserializer); + if (ret != 0) { + fpdlink_bridge->connector.status = + connector_status_disconnected; + DRM_INFO("Not able to detect Deserializer at %pOF\n", + fpdlink_bridge->fpdlink_node); + goto done; + } + + ret = fpdlink_dev_link_detect(serializer); + if (ret != true) { + fpdlink_bridge->connector.status = + connector_status_disconnected; + goto done; + } + + fpdlink_bridge->link_status = detected; + fpdlink_bridge->pixel_clk_status = not_detected; + fpdlink_bridge->connector.status = connector_status_connected; +done: + mutex_unlock(&fpdlink_bridge->lock); +} + +static void fpdlink_enable(struct drm_bridge *bridge) +{ + struct fpdlink *fpdlink_bridge = drm_bridge_to_fpdlink(bridge); + int ret = 0; + struct fpdlink_dev *serializer = fpdlink_bridge->serializer; + struct fpdlink_dev *deserializer = fpdlink_bridge->deserializer; + + mutex_lock(&fpdlink_bridge->lock); + + if (fpdlink_bridge->link_status == not_detected) { + fpdlink_bridge->connector.status = + connector_status_disconnected; + fpdlink_bridge->pixel_clk_status = not_detected; + goto done; + } + + if (fpdlink_bridge->enabled) + goto done; + + ret = fpdlink_dev_pixel_clk_detect(serializer); + + if (ret != true) { + fpdlink_bridge->connector.status = + connector_status_disconnected; + fpdlink_bridge->pixel_clk_status = not_detected; + pr_err("pixel_clk_status not detected\n"); + goto done; + } + + fpdlink_bridge->pixel_clk_status = detected; + fpdlink_bridge->connector.status = connector_status_connected; + fpdlink_dev_config(serializer); + fpdlink_dev_config(deserializer); + fpdlink_bridge->enabled = 1; + +done: + mutex_unlock(&fpdlink_bridge->lock); +} + +static int fpdlink_get_modes(struct drm_connector *connector) +{ + struct fpdlink *fpdlink_bridge = drm_connector_to_fpdlink(connector); + + if (fpdlink_bridge->enabled == 0) + fpdlink_enable(&fpdlink_bridge->bridge); + + return 0; +} +static const struct drm_connector_helper_funcs fpdlink_con_helper_funcs = { + .get_modes = fpdlink_get_modes, +}; + + +static int fpdlink_attach(struct drm_bridge *bridge) +{ + struct fpdlink *fpdlink_bridge = drm_bridge_to_fpdlink(bridge); + struct device_node *fpdlink_node = bridge->of_node; + struct device_node *serializer_node; + struct device_node *deserializer_node; + struct drm_bridge *serializer_bridge = NULL; + struct drm_bridge *deserializer_bridge = NULL; + struct drm_connector *connector; + int ret; + + if (!bridge->encoder) { + DRM_ERROR("Missing encoder\n"); + return -ENODEV; + } + connector = dev_get_drvdata(bridge->dev->dev); + fpdlink_node = fpdlink_bridge->fpdlink_node; + + drm_connector_helper_add(&fpdlink_bridge->connector, + &fpdlink_con_helper_funcs); + + ret = drm_connector_init(bridge->dev, &fpdlink_bridge->connector, + &fpdlink_con_funcs, DRM_MODE_CONNECTOR_VGA); + if (ret) { + DRM_ERROR("Failed to initialize connector\n"); + return ret; + } + + drm_connector_attach_encoder(&fpdlink_bridge->connector, + bridge->encoder); + + serializer_node = of_parse_phandle(fpdlink_node, + "fpdlink-serializer-i2c-handle", 0); + if (!serializer_node) { + DRM_INFO("failed to find fpdlink-serializer-i2c-handle node at %pOF\n", + fpdlink_node); + return -ENODEV; + } + + serializer_bridge = of_drm_find_bridge(serializer_node); + + + if (!serializer_bridge) { + DRM_INFO("failed to find serializer bridge for %pOF\n", + serializer_node); + return -ENODEV; + } + + fpdlink_bridge->serializer = + drm_bridge_to_fpdlink_dev(serializer_bridge); + fpdlink_bridge->serializer->index = bridge->encoder->index; + + deserializer_node = of_parse_phandle(fpdlink_node, + "fpdlink-deserializer-i2c-handle", 0); + if (!deserializer_node) { + DRM_INFO("failed to find fpdlink-deserializer-i2c-handle node at %pOF\n", + fpdlink_node); + return -ENODEV; + } + + deserializer_bridge = of_drm_find_bridge(deserializer_node); + if (!deserializer_bridge) { + DRM_INFO("failed to find deserializer bridge for %pOF\n", + deserializer_node); + return -ENODEV; + } + + fpdlink_bridge->deserializer = + drm_bridge_to_fpdlink_dev(deserializer_bridge); + fpdlink_bridge->deserializer->index = bridge->encoder->index; + fpdlink_bridge->index = bridge->encoder->index; + fpdlink_bridge->link_status = not_detected; + fpdlink_bridge->pixel_clk_status = not_detected; + return ret; +} + +static const struct drm_bridge_funcs fpdlink_bridge_funcs = { + .attach = fpdlink_attach, + .pre_enable = fpdlink_pre_enable, + .enable = fpdlink_enable, +}; + + + +static int fpdlink_probe(struct platform_device *pdev) +{ + struct fpdlink *fpdlink_bridge; + int gpio_pin; + struct device_node *np; + + fpdlink_bridge = devm_kzalloc(&pdev->dev, sizeof(*fpdlink_bridge), + GFP_KERNEL); + if (!fpdlink_bridge) + return -ENOMEM; + + mutex_init(&fpdlink_bridge->lock); + + + platform_set_drvdata(pdev, fpdlink_bridge); + + fpdlink_bridge->bridge.funcs = &fpdlink_bridge_funcs; + np = pdev->dev.of_node; + fpdlink_bridge->bridge.of_node = np; + fpdlink_bridge->fpdlink_node = np; + + gpio_pin = of_get_named_gpio(np, "disp-en-pin", 0); + + drm_bridge_add(&fpdlink_bridge->bridge); + return 0; +} + +static int fpdlink_remove(struct platform_device *pdev) +{ + struct fpdlink *fpdlink_bridge = platform_get_drvdata(pdev); + + drm_bridge_remove(&fpdlink_bridge->bridge); + return 0; +} + +static const struct of_device_id fpdlink_match[] = { + { .compatible = "ti,fpdlink" }, + {}, +}; +MODULE_DEVICE_TABLE(of, fpdlink_match); + +static struct platform_driver ti_fpdlink_driver = { + .probe = fpdlink_probe, + .remove = fpdlink_remove, + .driver = { + .name = "ti-fpdlink", + .of_match_table = fpdlink_match, + }, +}; +module_platform_driver(ti_fpdlink_driver); + +MODULE_AUTHOR("Vince Kim "); +MODULE_DESCRIPTION("TI FPDlink driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/ti-fpdlink/ti-fpdlink.h b/drivers/gpu/drm/bridge/ti-fpdlink/ti-fpdlink.h new file mode 100644 index 000000000000..073d4b4601f3 --- /dev/null +++ b/drivers/gpu/drm/bridge/ti-fpdlink/ti-fpdlink.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * TI FPD-LinkIII interface bridge driver header file + * + * Copyright (C) 2019 Lucid Motors Inc. + * + * Contact: Vince Kim + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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 __TI_FPDLINK_H__ +#define __TI_FPDLINK_H__ +#include +#include +#include +#include + +struct fpdlink_dev; + +enum clk_status { + not_detected = 0, + detected = 1, +}; + +struct fpdlink_dev_funcs { + int (*enable)(struct fpdlink_dev *dev); + void (*disable)(struct fpdlink_dev *dev); + int (*reset)(struct fpdlink_dev *dev); + int (*enable_i2c_passthrough)(struct fpdlink_dev *dev); + bool (*link_detect)(struct fpdlink_dev *dev); + bool (*pixel_clk_detect)(struct fpdlink_dev *dev); + int (*config)(struct fpdlink_dev *dev); + int (*reg_write)(struct fpdlink_dev *dev, u8 reg, u8 val); +}; + +struct fpdlink_dev { + struct drm_bridge bridge; + struct drm_connector connector; + struct device *dev; + struct i2c_client *client; + struct regmap *regmap; + struct mutex lock; + + const struct fpdlink_dev_funcs *funcs; + + char gpio_name[20]; + unsigned int index; + bool detected; + int gpio_power_en; + enum clk_status pixel_clk_status; + enum clk_status link_status; + int config_array_size; + u8 *config_array; +}; + +static inline struct fpdlink_dev * +drm_bridge_to_fpdlink_dev(struct drm_bridge *bridge) +{ + return container_of(bridge, struct fpdlink_dev, bridge); +} +#endif -- 2.17.1