[PATCH 4/7] mfd: ds90ux9xx: add TI DS90Ux9xx de-/serializer MFD driver

From: Vladimir Zapolskiy
Date: Mon Oct 08 2018 - 17:12:19 EST


From: Vladimir Zapolskiy <vladimir_zapolskiy@xxxxxxxxxx>

The change adds I2C device driver for TI DS90Ux9xx de-/serializers,
support of subdevice controllers is done in separate drivers, because
not all IC functionality may be needed in particular situations, and
this can be fine grained controlled in device tree.

The development of the driver was a collaborative work, the
contribution done by Balasubramani Vivekanandan includes:
* original implementation of the driver based on a reference driver,
* regmap powered interrupt controller support on serializers,
* support of implicitly or improperly specified in device tree ICs,
* support of device properties and attributes: backward compatible
mode, low frequency operation mode, spread spectrum clock generator.

Contribution by Steve Longerbeam:
* added ds90ux9xx_read_indirect() function,
* moved number of links property and added ds90ux9xx_num_fpd_links(),
* moved and updated ds90ux9xx_get_link_status() function to core driver,
* added fpd_link_show device attribute.

Sandeep Jain added support of pixel clock edge configuration.

Signed-off-by: Vladimir Zapolskiy <vladimir_zapolskiy@xxxxxxxxxx>
---
drivers/mfd/Kconfig | 14 +
drivers/mfd/Makefile | 1 +
drivers/mfd/ds90ux9xx-core.c | 879 ++++++++++++++++++++++++++++++++++
include/linux/mfd/ds90ux9xx.h | 42 ++
4 files changed, 936 insertions(+)
create mode 100644 drivers/mfd/ds90ux9xx-core.c
create mode 100644 include/linux/mfd/ds90ux9xx.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 8c5dfdce4326..a969fa123f64 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1280,6 +1280,20 @@ config MFD_DM355EVM_MSP
boards. MSP430 firmware manages resets and power sequencing,
inputs from buttons and the IR remote, LEDs, an RTC, and more.

+config MFD_DS90UX9XX
+ tristate "TI DS90Ux9xx FPD-Link de-/serializer driver"
+ depends on I2C && OF
+ select MFD_CORE
+ select REGMAP_I2C
+ help
+ Say yes here to enable support for TI DS90UX9XX de-/serializer ICs.
+
+ This driver provides basic support for setting up the de-/serializer
+ chips. Additional functionalities like connection handling to
+ remote de-/serializers, I2C bridging, pin multiplexing, GPIO
+ controller and so on are provided by separate drivers and should
+ enabled individually.
+
config MFD_LP3943
tristate "TI/National Semiconductor LP3943 MFD Driver"
depends on I2C
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 12980a4ad460..cc92bf5394b7 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -224,6 +224,7 @@ obj-$(CONFIG_MFD_HI655X_PMIC) += hi655x-pmic.o
obj-$(CONFIG_MFD_DLN2) += dln2.o
obj-$(CONFIG_MFD_RT5033) += rt5033.o
obj-$(CONFIG_MFD_SKY81452) += sky81452.o
+obj-$(CONFIG_MFD_DS90UX9XX) += ds90ux9xx-core.o

intel-soc-pmic-objs := intel_soc_pmic_core.o intel_soc_pmic_crc.o
obj-$(CONFIG_INTEL_SOC_PMIC) += intel-soc-pmic.o
diff --git a/drivers/mfd/ds90ux9xx-core.c b/drivers/mfd/ds90ux9xx-core.c
new file mode 100644
index 000000000000..ad96c109a451
--- /dev/null
+++ b/drivers/mfd/ds90ux9xx-core.c
@@ -0,0 +1,879 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * TI DS90Ux9xx MFD driver
+ *
+ * Copyright (c) 2017-2018 Mentor Graphics Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/ds90ux9xx.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+
+/* Serializer Registers */
+#define SER_REG_MODE_SELECT 0x04
+#define SER_REG_GEN_STATUS 0x0C
+#define SER_REG_CTRL_STATUS 0x13
+
+/* Deserializer Registers */
+#define DES_REG_GEN_CONFIG0 0x02
+#define DES_REG_GEN_STATUS 0x1C
+#define DES_REG_MODE_STATUS 0x23
+#define DES_REG_SSCG_CTRL 0x2C
+#define DES_REG_DUAL_RX_CTRL 0x34
+#define DES_REG_MAPSEL 0x49
+#define DES_REG_INDIRECT_ADDR 0x6C
+#define DES_REG_INDIRECT_DATA 0x6D
+
+#define DES_FPD_MODE_MASK (BIT(4) | BIT(3))
+#define DES_FPD_HW_MODE_AUTO 0x0
+#define DES_FPD_HW_MODE_PRIMARY BIT(4)
+#define DES_FPD_HW_MODE_SECONDARY (BIT(4) | BIT(3))
+
+#define SSCG_REG_MASK 0x0F
+#define SSCG_MAX_VALUE 0x7
+#define SSCG_ENABLE BIT(3)
+
+/* Common Registers and bitfields */
+#define SER_DES_REG_CONFIG 0x03
+
+#define SER_DE_GATE_RGB BIT(4)
+#define DES_DE_GATE_RGB BIT(1)
+
+#define PIXEL_CLK_EDGE_RISING BIT(0)
+
+#define MAPSEL_MASK (BIT(6) | BIT(5))
+#define MAPSEL_SET BIT(6)
+#define MAPSEL_MSB BIT(5)
+
+#define BKWD_MODE_OVERRIDE BIT(3)
+#define BKWD_MODE_ENABLED BIT(2)
+#define LF_MODE_OVERRIDE BIT(1)
+#define LF_MODE_ENABLED BIT(0)
+
+#define LF_MODE_PIN_STATUS BIT(3)
+#define BKWD_MODE_PIN_STATUS BIT(1)
+
+#define GEN_STATUS_LOCK BIT(0)
+
+#define SER_DES_DEVICE_ID_REG 0xF0
+#define DEVICE_ID_LEN 6
+
+enum ds90ux9xx_capability {
+ DS90UX9XX_CAP_SERIALIZER,
+ DS90UX9XX_CAP_HDCP,
+ DS90UX9XX_CAP_MODES,
+ DS90UX9XX_CAP_SSCG,
+ DS90UX9XX_CAP_PIXEL_CLK_EDGE,
+ DS90UX9XX_CAP_MAPSEL,
+ DS90UX9XX_CAP_DE_GATE,
+};
+
+struct ds90ux9xx_device_property {
+ const char id[DEVICE_ID_LEN];
+ unsigned int num_links;
+ const u32 caps;
+};
+
+struct ds90ux9xx {
+ struct device *dev;
+ struct regmap *regmap;
+ struct mutex indirect; /* serializes access to indirect registers */
+ struct gpio_desc *power_gpio;
+ enum ds90ux9xx_device_id dev_id;
+ const struct ds90ux9xx_device_property *property;
+};
+
+#define TO_CAP(_cap, _enabled) (_enabled ? BIT(DS90UX9XX_CAP_ ## _cap) : 0x0)
+
+#define DS90UX9XX_DEVICE(_id, _links, _ser, _hdcp, _modes, _sscg, \
+ _pixel, _mapsel, _de_gate) \
+ [TI_DS90 ## _id] = { \
+ .id = "_" __stringify(_id), \
+ .num_links = _links, \
+ .caps = \
+ TO_CAP(SERIALIZER, _ser) | \
+ TO_CAP(HDCP, _hdcp) | \
+ TO_CAP(MODES, _modes) | \
+ TO_CAP(SSCG, _sscg) | \
+ TO_CAP(PIXEL_CLK_EDGE, _pixel) | \
+ TO_CAP(MAPSEL, _mapsel) | \
+ TO_CAP(DE_GATE, _de_gate) \
+ }
+
+static const struct ds90ux9xx_device_property ds90ux9xx_devices[] = {
+ /*
+ * List of TI DS90Ux9xx properties:
+ * # FPD-links, serializer, HDCP, BW/LF modes, SSCG,
+ * pixel clock edge, mapsel, RGB DE Gate
+ */
+ DS90UX9XX_DEVICE(UB925, 1, 1, 0, 1, 0, 1, 0, 1),
+ DS90UX9XX_DEVICE(UH925, 1, 1, 1, 1, 0, 1, 0, 1),
+ DS90UX9XX_DEVICE(UB927, 1, 1, 0, 1, 0, 0, 1, 1),
+ DS90UX9XX_DEVICE(UH927, 1, 1, 1, 1, 0, 0, 1, 1),
+ DS90UX9XX_DEVICE(UB926, 1, 0, 0, 1, 1, 1, 0, 0),
+ DS90UX9XX_DEVICE(UH926, 1, 0, 1, 1, 1, 1, 0, 0),
+ DS90UX9XX_DEVICE(UB928, 1, 0, 0, 1, 0, 0, 1, 0),
+ DS90UX9XX_DEVICE(UH928, 1, 0, 1, 1, 0, 0, 1, 0),
+ DS90UX9XX_DEVICE(UB940, 2, 0, 0, 0, 0, 0, 0, 1),
+ DS90UX9XX_DEVICE(UH940, 2, 0, 1, 0, 0, 0, 0, 1),
+};
+
+static const struct regmap_config ds90ux9xx_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0xFF,
+ .cache_type = REGCACHE_NONE,
+};
+
+static bool ds90ux9xx_has_capability(struct ds90ux9xx *ds90ux9xx,
+ enum ds90ux9xx_capability cap)
+{
+ return ds90ux9xx->property->caps & BIT(cap);
+}
+
+bool ds90ux9xx_is_serializer(struct device *dev)
+{
+ struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev);
+
+ return ds90ux9xx_has_capability(ds90ux9xx, DS90UX9XX_CAP_SERIALIZER);
+}
+EXPORT_SYMBOL_GPL(ds90ux9xx_is_serializer);
+
+unsigned int ds90ux9xx_num_fpd_links(struct device *dev)
+{
+ struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev);
+
+ return ds90ux9xx->property->num_links;
+}
+EXPORT_SYMBOL_GPL(ds90ux9xx_num_fpd_links);
+
+int ds90ux9xx_update_bits_indirect(struct device *dev, u8 reg, u8 mask, u8 val)
+{
+ struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev);
+ int ret;
+
+ mutex_lock(&ds90ux9xx->indirect);
+
+ ret = regmap_write(ds90ux9xx->regmap, DES_REG_INDIRECT_ADDR, reg);
+ if (ret) {
+ mutex_unlock(&ds90ux9xx->indirect);
+ return ret;
+ }
+
+ ret = regmap_update_bits(ds90ux9xx->regmap, DES_REG_INDIRECT_DATA,
+ mask, val);
+
+ mutex_unlock(&ds90ux9xx->indirect);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ds90ux9xx_update_bits_indirect);
+
+int ds90ux9xx_write_indirect(struct device *dev, u8 reg, u8 val)
+{
+ struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev);
+ int ret;
+
+ mutex_lock(&ds90ux9xx->indirect);
+
+ ret = regmap_write(ds90ux9xx->regmap, DES_REG_INDIRECT_ADDR, reg);
+ if (ret) {
+ mutex_unlock(&ds90ux9xx->indirect);
+ return ret;
+ }
+
+ ret = regmap_write(ds90ux9xx->regmap, DES_REG_INDIRECT_DATA, val);
+
+ mutex_unlock(&ds90ux9xx->indirect);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ds90ux9xx_write_indirect);
+
+int ds90ux9xx_read_indirect(struct device *dev, u8 reg, u8 *val)
+{
+ struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev);
+ unsigned int data;
+ int ret;
+
+ mutex_lock(&ds90ux9xx->indirect);
+
+ ret = regmap_write(ds90ux9xx->regmap, DES_REG_INDIRECT_ADDR, reg);
+ if (ret) {
+ mutex_unlock(&ds90ux9xx->indirect);
+ return ret;
+ }
+
+ ret = regmap_read(ds90ux9xx->regmap, DES_REG_INDIRECT_DATA, &data);
+
+ mutex_unlock(&ds90ux9xx->indirect);
+
+ if (!ret)
+ *val = data;
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ds90ux9xx_read_indirect);
+
+static int ds90ux9xx_read_active_link(struct ds90ux9xx *ds90ux9xx,
+ unsigned int *link)
+{
+ unsigned int val;
+ int ret;
+
+ if (ds90ux9xx->property->num_links == 1) {
+ *link = 0;
+ return 0;
+ }
+
+ ret = regmap_read(ds90ux9xx->regmap, DES_REG_DUAL_RX_CTRL, &val);
+ if (ret) {
+ dev_err(ds90ux9xx->dev, "Failed to read FPD mode\n");
+ return ret;
+ }
+
+ switch (val & DES_FPD_MODE_MASK) {
+ case DES_FPD_HW_MODE_AUTO:
+ case DES_FPD_HW_MODE_PRIMARY:
+ *link = 0;
+ break;
+ case DES_FPD_HW_MODE_SECONDARY:
+ *link = 1;
+ break;
+ default:
+ dev_err(ds90ux9xx->dev, "Unhandled FPD mode\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* Note that lock status is reported if video pattern generator is enabled */
+int ds90ux9xx_get_link_status(struct device *dev, unsigned int *link,
+ bool *locked)
+{
+ struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev);
+ unsigned int reg, status;
+ int ret;
+
+ ret = ds90ux9xx_read_active_link(ds90ux9xx, link);
+ if (ret)
+ return ret;
+
+ if (ds90ux9xx_is_serializer(dev))
+ reg = SER_REG_GEN_STATUS;
+ else
+ reg = DES_REG_GEN_STATUS;
+
+ ret = regmap_read(ds90ux9xx->regmap, reg, &status);
+ if (ret) {
+ dev_err(dev, "Unable to get lock status\n");
+ return ret;
+ }
+
+ *locked = status & GEN_STATUS_LOCK ? true : false;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ds90ux9xx_get_link_status);
+
+enum ds90ux9xx_device_id ds90ux9xx_get_ic_type(struct device *dev)
+{
+ struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev);
+
+ return ds90ux9xx->dev_id;
+}
+EXPORT_SYMBOL_GPL(ds90ux9xx_get_ic_type);
+
+static int ds90ux9xx_set_de_gate(struct ds90ux9xx *ds90ux9xx, bool enable)
+{
+ unsigned int reg, val;
+
+ if (!ds90ux9xx_has_capability(ds90ux9xx, DS90UX9XX_CAP_DE_GATE))
+ return 0;
+
+ if (ds90ux9xx_is_serializer(ds90ux9xx->dev)) {
+ reg = SER_REG_MODE_SELECT;
+ val = SER_DE_GATE_RGB;
+ } else {
+ reg = SER_DES_REG_CONFIG;
+ val = DES_DE_GATE_RGB;
+ }
+
+ return regmap_update_bits(ds90ux9xx->regmap, reg, val,
+ enable ? val : 0x0);
+}
+
+static int ds90ux9xx_set_bcmode(struct ds90ux9xx *ds90ux9xx, bool enable)
+{
+ unsigned int reg, val;
+ int ret;
+
+ if (ds90ux9xx_is_serializer(ds90ux9xx->dev))
+ reg = SER_REG_MODE_SELECT;
+ else
+ reg = DES_REG_GEN_CONFIG0;
+
+ val = BKWD_MODE_OVERRIDE;
+ if (enable)
+ val |= BKWD_MODE_ENABLED;
+
+ ret = regmap_update_bits(ds90ux9xx->regmap, reg,
+ BKWD_MODE_OVERRIDE | BKWD_MODE_ENABLED, val);
+ if (ret)
+ return ret;
+
+ return ds90ux9xx_set_de_gate(ds90ux9xx, !enable);
+}
+
+static int ds90ux9xx_set_lfmode(struct ds90ux9xx *ds90ux9xx, bool enable)
+{
+ unsigned int reg, val;
+
+ if (ds90ux9xx_is_serializer(ds90ux9xx->dev))
+ reg = SER_REG_MODE_SELECT;
+ else
+ reg = DES_REG_GEN_CONFIG0;
+
+ val = LF_MODE_OVERRIDE;
+ if (enable)
+ val |= LF_MODE_ENABLED;
+
+ return regmap_update_bits(ds90ux9xx->regmap, reg,
+ LF_MODE_OVERRIDE | LF_MODE_ENABLED, val);
+}
+
+static int ds90ux9xx_set_sscg(struct ds90ux9xx *ds90ux9xx, unsigned int val)
+{
+ if ((val & ~SSCG_ENABLE) > SSCG_MAX_VALUE) {
+ dev_err(ds90ux9xx->dev, "Invalid SSCG value: %#x", val);
+ return -EINVAL;
+ }
+
+ return regmap_write(ds90ux9xx->regmap, DES_REG_SSCG_CTRL, val);
+}
+
+static int ds90ux9xx_set_pixel_clk_edge(struct ds90ux9xx *ds90ux9xx,
+ bool rising)
+{
+ return regmap_update_bits(ds90ux9xx->regmap, SER_DES_REG_CONFIG,
+ PIXEL_CLK_EDGE_RISING,
+ rising ? PIXEL_CLK_EDGE_RISING : 0x0);
+}
+
+static ssize_t backward_compatible_mode_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev);
+ unsigned int cfg_reg, sts_reg, val;
+ int ret, bcmode;
+
+ if (ds90ux9xx_is_serializer(ds90ux9xx->dev)) {
+ cfg_reg = SER_REG_MODE_SELECT;
+ sts_reg = SER_REG_CTRL_STATUS;
+ } else {
+ cfg_reg = DES_REG_GEN_CONFIG0;
+ sts_reg = DES_REG_MODE_STATUS;
+ }
+
+ ret = regmap_read(ds90ux9xx->regmap, cfg_reg, &val);
+ if (ret)
+ return ret;
+
+ if (val & BKWD_MODE_OVERRIDE) {
+ bcmode = val & BKWD_MODE_ENABLED;
+ } else {
+ /* Read mode from pin status */
+ ret = regmap_read(ds90ux9xx->regmap, sts_reg, &val);
+ if (ret)
+ return ret;
+
+ bcmode = val & BKWD_MODE_PIN_STATUS;
+ }
+
+ return sprintf(buf, "%d\n", bcmode ? 1 : 0);
+}
+
+static ssize_t backward_compatible_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev);
+ unsigned int val;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ ret = ds90ux9xx_set_bcmode(ds90ux9xx, val);
+ if (ret)
+ return ret;
+
+ return count;
+}
+static DEVICE_ATTR_RW(backward_compatible_mode);
+
+static ssize_t low_frequency_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev);
+ unsigned int cfg_reg, sts_reg, val;
+ int ret, lfmode;
+
+ if (ds90ux9xx_is_serializer(ds90ux9xx->dev)) {
+ cfg_reg = SER_REG_MODE_SELECT;
+ sts_reg = SER_REG_CTRL_STATUS;
+ } else {
+ cfg_reg = DES_REG_GEN_CONFIG0;
+ sts_reg = DES_REG_MODE_STATUS;
+ }
+
+ ret = regmap_read(ds90ux9xx->regmap, cfg_reg, &val);
+ if (ret)
+ return ret;
+
+ if (val & LF_MODE_OVERRIDE) {
+ lfmode = val & LF_MODE_ENABLED;
+ } else {
+ /* Read mode from pin status */
+ ret = regmap_read(ds90ux9xx->regmap, sts_reg, &val);
+ if (ret)
+ return ret;
+
+ lfmode = val & LF_MODE_PIN_STATUS;
+ }
+
+ return sprintf(buf, "%d\n", lfmode ? 1 : 0);
+}
+
+static ssize_t low_frequency_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev);
+ unsigned int val;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ ret = ds90ux9xx_set_lfmode(ds90ux9xx, val);
+ if (ret)
+ return ret;
+
+ return count;
+}
+static DEVICE_ATTR_RW(low_frequency_mode);
+
+static ssize_t sscg_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(ds90ux9xx->regmap, DES_REG_SSCG_CTRL, &val);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%d\n", val & SSCG_REG_MASK);
+}
+
+static ssize_t sscg_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev);
+ int ret, val;
+
+ ret = kstrtoint(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ ret = ds90ux9xx_set_sscg(ds90ux9xx, val);
+ if (ret)
+ return ret;
+
+ return count;
+}
+static DEVICE_ATTR_RW(sscg);
+
+static ssize_t pixel_clock_edge_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(ds90ux9xx->regmap, SER_DES_REG_CONFIG, &val);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%s\n",
+ (val & PIXEL_CLK_EDGE_RISING) ? "rising" : "falling");
+}
+
+static ssize_t pixel_clock_edge_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev);
+ bool rising;
+ int ret;
+
+ if (sysfs_streq(buf, "rising") || sysfs_streq(buf, "1"))
+ rising = true;
+ else if (sysfs_streq(buf, "falling") || sysfs_streq(buf, "0"))
+ rising = false;
+ else
+ return -EINVAL;
+
+ ret = ds90ux9xx_set_pixel_clk_edge(ds90ux9xx, rising);
+ if (ret)
+ return ret;
+
+ return count;
+}
+static DEVICE_ATTR_RW(pixel_clock_edge);
+
+static ssize_t link_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned int link;
+ bool locked;
+ int ret;
+
+ ret = ds90ux9xx_get_link_status(dev, &link, &locked);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%u: %s\n", link, locked ? "locked" : "unlocked");
+}
+static DEVICE_ATTR_RO(link);
+
+static struct attribute *ds90ux9xx_attributes[] = {
+ &dev_attr_backward_compatible_mode.attr,
+ &dev_attr_low_frequency_mode.attr,
+ &dev_attr_sscg.attr,
+ &dev_attr_pixel_clock_edge.attr,
+ &dev_attr_link.attr,
+ NULL,
+};
+
+static umode_t ds90ux9xx_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int index)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev);
+ enum ds90ux9xx_capability cap;
+
+ /* Attribute indices match the ds90ux9xx_attributes[] array indices */
+ switch (index) {
+ case 0: /* Backward compatible mode */
+ case 1: /* Low frequency mode */
+ cap = DS90UX9XX_CAP_MODES;
+ break;
+ case 2: /* Spread spectrum clock generator */
+ cap = DS90UX9XX_CAP_SSCG;
+ break;
+ case 3: /* Pixel clock edge */
+ cap = DS90UX9XX_CAP_PIXEL_CLK_EDGE;
+ break;
+ case 4: /* FPD-III link lock status - visible for all chips */
+ return attr->mode;
+ default:
+ return 0;
+ }
+
+ return ds90ux9xx_has_capability(ds90ux9xx, cap) ? attr->mode : 0;
+}
+
+static const struct attribute_group ds90ux9xx_attr_group = {
+ .attrs = ds90ux9xx_attributes,
+ .is_visible = ds90ux9xx_attr_is_visible,
+};
+__ATTRIBUTE_GROUPS(ds90ux9xx_attr);
+
+static int ds90ux9xx_config_modes(struct ds90ux9xx *ds90ux9xx)
+{
+ struct device_node *np = ds90ux9xx->dev->of_node;
+ u32 mode;
+ int ret;
+
+ if (!ds90ux9xx_has_capability(ds90ux9xx, DS90UX9XX_CAP_MODES))
+ return 0;
+
+ ret = of_property_read_u32(np, "ti,backward-compatible-mode", &mode);
+ if (!ret) {
+ ret = ds90ux9xx_set_bcmode(ds90ux9xx, mode);
+ if (ret)
+ return ret;
+ } else {
+ ret = ds90ux9xx_set_de_gate(ds90ux9xx, true);
+ if (ret)
+ return ret;
+ }
+
+ ret = of_property_read_u32(np, "ti,low-frequency-mode", &mode);
+ if (!ret) {
+ ret = ds90ux9xx_set_lfmode(ds90ux9xx, mode);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ds90ux9xx_config_sscg(struct ds90ux9xx *ds90ux9xx)
+{
+ struct device_node *np = ds90ux9xx->dev->of_node;
+ u32 sscg;
+ int ret;
+
+ if (!ds90ux9xx_has_capability(ds90ux9xx, DS90UX9XX_CAP_SSCG))
+ return 0;
+
+ ret = of_property_read_u32(np, "ti,spread-spectrum-clock-generation",
+ &sscg);
+ if (ret)
+ return 0;
+
+ return ds90ux9xx_set_sscg(ds90ux9xx, sscg);
+}
+
+static int ds90ux9xx_config_pixel_clk_edge(struct ds90ux9xx *ds90ux9xx)
+{
+ struct device_node *np = ds90ux9xx->dev->of_node;
+ const char *pixel;
+ bool rising;
+
+ if (!ds90ux9xx_has_capability(ds90ux9xx, DS90UX9XX_CAP_PIXEL_CLK_EDGE))
+ return 0;
+
+ if (of_property_read_string(np, "ti,pixel-clock-edge", &pixel))
+ return 0;
+
+ if (!strcmp(pixel, "rising"))
+ rising = true;
+ else if (!strcmp(pixel, "falling"))
+ rising = false;
+ else
+ return -EINVAL;
+
+ return ds90ux9xx_set_pixel_clk_edge(ds90ux9xx, rising);
+}
+
+static int ds90ux9xx_config_map_select(struct ds90ux9xx *ds90ux9xx)
+{
+ struct device_node *np = ds90ux9xx->dev->of_node;
+ unsigned int reg, val;
+
+ if (!ds90ux9xx_has_capability(ds90ux9xx, DS90UX9XX_CAP_MAPSEL))
+ return 0;
+
+ if (ds90ux9xx_is_serializer(ds90ux9xx->dev))
+ reg = SER_REG_CTRL_STATUS;
+ else
+ reg = DES_REG_MAPSEL;
+
+ if (of_property_read_bool(np, "ti,video-map-select-msb"))
+ val = MAPSEL_SET | MAPSEL_MSB;
+ else if (of_property_read_bool(np, "ti,video-map-select-lsb"))
+ val = MAPSEL_SET;
+ else
+ val = 0;
+
+ return regmap_update_bits(ds90ux9xx->regmap, reg, MAPSEL_MASK, val);
+}
+
+static int ds90ux9xx_config_properties(struct ds90ux9xx *ds90ux9xx)
+{
+ int ret;
+
+ ret = ds90ux9xx_config_modes(ds90ux9xx);
+ if (ret)
+ return ret;
+
+ ret = ds90ux9xx_config_sscg(ds90ux9xx);
+ if (ret)
+ return ret;
+
+ ret = ds90ux9xx_config_pixel_clk_edge(ds90ux9xx);
+ if (ret)
+ return ret;
+
+ return ds90ux9xx_config_map_select(ds90ux9xx);
+}
+
+static const struct i2c_device_id ds90ux9xx_ids[] = {
+ /* Supported serializers */
+ { "ds90ub925q", TI_DS90UB925 },
+ { "ds90uh925q", TI_DS90UH925 },
+ { "ds90ub927q", TI_DS90UB927 },
+ { "ds90uh927q", TI_DS90UH927 },
+
+ /* Supported deserializers */
+ { "ds90ub926q", TI_DS90UB926 },
+ { "ds90uh926q", TI_DS90UH926 },
+ { "ds90ub928q", TI_DS90UB928 },
+ { "ds90uh928q", TI_DS90UH928 },
+ { "ds90ub940q", TI_DS90UB940 },
+ { "ds90uh940q", TI_DS90UH940 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, ds90ux9xx_ids);
+
+static const struct of_device_id ds90ux9xx_dt_ids[] = {
+ { .compatible = "ti,ds90ux9xx", },
+ { .compatible = "ti,ds90ub925q", },
+ { .compatible = "ti,ds90uh925q", },
+ { .compatible = "ti,ds90ub927q", },
+ { .compatible = "ti,ds90uh927q", },
+ { .compatible = "ti,ds90ub926q", },
+ { .compatible = "ti,ds90uh926q", },
+ { .compatible = "ti,ds90ub928q", },
+ { .compatible = "ti,ds90uh928q", },
+ { .compatible = "ti,ds90ub940q", },
+ { .compatible = "ti,ds90uh940q", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ds90ux9xx_dt_ids);
+
+static int ds90ux9xx_read_ic_type(struct i2c_client *client,
+ struct ds90ux9xx *ds90ux9xx)
+{
+ u8 device_id[DEVICE_ID_LEN + 1] = { 0 };
+ const struct i2c_device_id *id;
+ const char *id_code;
+ unsigned int i;
+ int ret;
+
+ ret = regmap_raw_read(ds90ux9xx->regmap, SER_DES_DEVICE_ID_REG,
+ device_id, DEVICE_ID_LEN);
+ if (ret < 0) {
+ dev_err(ds90ux9xx->dev, "Failed to read device id: %d\n", ret);
+ return ret;
+ }
+
+ id = i2c_match_id(ds90ux9xx_ids, client);
+ if (id) {
+ id_code = ds90ux9xx_devices[id->driver_data].id;
+ if (!strncmp(device_id, id_code, DEVICE_ID_LEN)) {
+ ds90ux9xx->dev_id = id->driver_data;
+ return 0;
+ }
+ }
+
+ /* DS90UH925 device quirk */
+ if (!memcmp(device_id, "\0\0\0\0\0\0", DEVICE_ID_LEN)) {
+ if (id && id->driver_data != TI_DS90UH925)
+ dev_err(ds90ux9xx->dev,
+ "Device ID returned all zeroes, assuming device is DS90UH925\n");
+ ds90ux9xx->dev_id = TI_DS90UH925;
+ return 0;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ds90ux9xx_devices); i++) {
+ if (strncmp(device_id, ds90ux9xx_devices[i].id, DEVICE_ID_LEN))
+ continue;
+
+ if (id)
+ dev_err(ds90ux9xx->dev,
+ "Mismatch between probed device ID [%s] and HW device ID [%s]\n",
+ id_code, device_id);
+
+ ds90ux9xx->dev_id = i;
+ return 0;
+ }
+
+ dev_err(ds90ux9xx->dev, "Device ID [%s] is not supported\n", device_id);
+
+ return -ENODEV;
+}
+
+static int ds90ux9xx_probe(struct i2c_client *client)
+{
+ struct ds90ux9xx *ds90ux9xx;
+ int ret;
+
+ ds90ux9xx = devm_kzalloc(&client->dev, sizeof(*ds90ux9xx), GFP_KERNEL);
+ if (!ds90ux9xx)
+ return -ENOMEM;
+
+ /* Get optional GPIO connected to PDB pin */
+ ds90ux9xx->power_gpio = devm_gpiod_get_optional(&client->dev, "power",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(ds90ux9xx->power_gpio))
+ return PTR_ERR(ds90ux9xx->power_gpio);
+
+ /* Give time to the controller to initialize itself after power-up */
+ if (ds90ux9xx->power_gpio)
+ usleep_range(2000, 2500);
+
+ ds90ux9xx->dev = &client->dev;
+ ds90ux9xx->regmap = devm_regmap_init_i2c(client,
+ &ds90ux9xx_regmap_config);
+ if (IS_ERR(ds90ux9xx->regmap))
+ return PTR_ERR(ds90ux9xx->regmap);
+
+ mutex_init(&ds90ux9xx->indirect);
+
+ ret = ds90ux9xx_read_ic_type(client, ds90ux9xx);
+ if (ret)
+ return ret;
+
+ ds90ux9xx->property = &ds90ux9xx_devices[ds90ux9xx->dev_id];
+
+ i2c_set_clientdata(client, ds90ux9xx);
+
+ ret = ds90ux9xx_config_properties(ds90ux9xx);
+ if (ret)
+ return ret;
+
+ ret = sysfs_create_groups(&ds90ux9xx->dev->kobj, ds90ux9xx_attr_groups);
+ if (ret)
+ return ret;
+
+ ret = devm_of_platform_populate(ds90ux9xx->dev);
+ if (ret)
+ sysfs_remove_groups(&ds90ux9xx->dev->kobj,
+ ds90ux9xx_attr_groups);
+
+ return ret;
+}
+
+static int ds90ux9xx_remove(struct i2c_client *client)
+{
+ struct ds90ux9xx *ds90ux9xx = i2c_get_clientdata(client);
+
+ sysfs_remove_groups(&ds90ux9xx->dev->kobj, ds90ux9xx_attr_groups);
+
+ if (ds90ux9xx->power_gpio)
+ gpiod_set_value_cansleep(ds90ux9xx->power_gpio, 0);
+
+ return 0;
+}
+
+static struct i2c_driver ds90ux9xx_driver = {
+ .driver = {
+ .name = "ds90ux9xx",
+ .of_match_table = ds90ux9xx_dt_ids,
+ },
+ .probe_new = ds90ux9xx_probe,
+ .remove = ds90ux9xx_remove,
+ .id_table = ds90ux9xx_ids,
+};
+module_i2c_driver(ds90ux9xx_driver);
+
+MODULE_AUTHOR("Balasubramani Vivekanandan <balasubramani_vivekanandan@xxxxxxxxxx>");
+MODULE_AUTHOR("Vladimir Zapolskiy <vladimir_zapolskiy@xxxxxxxxxx>");
+MODULE_DESCRIPTION("TI DS90Ux9xx MFD driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/ds90ux9xx.h b/include/linux/mfd/ds90ux9xx.h
new file mode 100644
index 000000000000..72a5928b6ad1
--- /dev/null
+++ b/include/linux/mfd/ds90ux9xx.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * TI DS90Ux9xx MFD driver
+ *
+ * Copyright (c) 2017-2018 Mentor Graphics Inc.
+ */
+
+#ifndef __LINUX_MFD_DS90UX9XX_H
+#define __LINUX_MFD_DS90UX9XX_H
+
+#include <linux/types.h>
+
+enum ds90ux9xx_device_id {
+ /* Supported serializers */
+ TI_DS90UB925,
+ TI_DS90UH925,
+ TI_DS90UB927,
+ TI_DS90UH927,
+
+ /* Supported deserializers */
+ TI_DS90UB926,
+ TI_DS90UH926,
+ TI_DS90UB928,
+ TI_DS90UH928,
+ TI_DS90UB940,
+ TI_DS90UH940,
+};
+
+struct device;
+
+bool ds90ux9xx_is_serializer(struct device *dev);
+enum ds90ux9xx_device_id ds90ux9xx_get_ic_type(struct device *dev);
+unsigned int ds90ux9xx_num_fpd_links(struct device *dev);
+
+int ds90ux9xx_get_link_status(struct device *dev, unsigned int *link,
+ bool *locked);
+
+int ds90ux9xx_update_bits_indirect(struct device *dev, u8 reg, u8 mask, u8 val);
+int ds90ux9xx_write_indirect(struct device *dev, unsigned char reg, u8 val);
+int ds90ux9xx_read_indirect(struct device *dev, u8 reg, u8 *val);
+
+#endif /*__LINUX_MFD_DS90UX9XX_H */
--
2.17.1