2019-06-11 14:10:33

by Michael Drake

[permalink] [raw]
Subject: [PATCH v1 00/11] Add ti948 and ti949 display bridge drivers

This patch series adds support for the ti948 and ti949 display
bridge devices. They are both regmap i2c device drivers.

The ti949 converts HDMI video signals to FPD-Link III.
The ti948 converts FPD-Link III video signals to OpenLDI.

The drivers support PM suspend/resume, and rely on device tree /
ACPI nodes to set up any device dependency chain. (ACPI requiring
the special DT namespace link device ID, PRP0001.) The unified
device properties API is used to get board-specific config from
device tree / ACPI.

Cc: Patrick Glaser <[email protected]>
Cc: Nate Case <[email protected]>

Michael Drake (11):
dt-bindings: display/bridge: Add bindings for ti948
ti948: i2c device driver for TI DS90UB948-Q1
dt-bindings: display/bridge: Add config property for ti948
ti948: Add support for configuration via device properties
ti948: Add alive check function using schedule_delayed_work()
ti948: Reconfigure in the alive check when device returns
ti948: Add sysfs node for alive attribute
dt-bindings: display/bridge: Add bindings for ti949
ti949: i2c device driver for TI DS90UB949-Q1
dt-bindings: display/bridge: Add config property for ti949
ti949: Add support for configuration via device properties

.../bindings/display/bridge/ti,ds90ub948.txt | 45 ++
.../bindings/display/bridge/ti,ds90ub949.txt | 37 +
drivers/gpu/drm/bridge/Kconfig | 16 +
drivers/gpu/drm/bridge/Makefile | 2 +
drivers/gpu/drm/bridge/ti948.c | 689 ++++++++++++++++++
drivers/gpu/drm/bridge/ti949.c | 631 ++++++++++++++++
6 files changed, 1420 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/bridge/ti,ds90ub948.txt
create mode 100644 Documentation/devicetree/bindings/display/bridge/ti,ds90ub949.txt
create mode 100644 drivers/gpu/drm/bridge/ti948.c
create mode 100644 drivers/gpu/drm/bridge/ti949.c

--
2.20.1


2019-06-11 14:10:44

by Michael Drake

[permalink] [raw]
Subject: [PATCH v1 04/11] ti948: Add support for configuration via device properties

This allows the device to be configured for the board in
device tree, or in ACPI tables. The device node properties
can provide an array of register addresses and values to be
written to configure the device for the board.

The config is written to the device on probe and on PM resume.

Signed-off-by: Michael Drake <[email protected]>
Cc: Patrick Glaser <[email protected]>
Cc: Nate Case <[email protected]>
---
drivers/gpu/drm/bridge/ti948.c | 106 ++++++++++++++++++++++++++++++++-
1 file changed, 105 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/bridge/ti948.c b/drivers/gpu/drm/bridge/ti948.c
index c22252036bbe..9cb37215f049 100644
--- a/drivers/gpu/drm/bridge/ti948.c
+++ b/drivers/gpu/drm/bridge/ti948.c
@@ -19,6 +19,7 @@
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/delay.h>
+#include <linux/slab.h>
#include <linux/i2c.h>

/* Number of times to try checking for device on bringup. */
@@ -122,6 +123,8 @@ struct ti948_reg_val {
* struct ti948_ctx - ti948 driver context
* @i2c: Handle for the device's i2c client.
* @regmap: Handle for the device's regmap.
+ * @config: Array of register values loaded from device properties.
+ * @config_len: Number of entries in config.
* @reg_names: Array of regulator names, or NULL.
* @regs: Array of regulators, or NULL.
* @reg_count: Number of entries in reg_names and regs arrays.
@@ -129,6 +132,8 @@ struct ti948_reg_val {
struct ti948_ctx {
struct i2c_client *i2c;
struct regmap *regmap;
+ struct ti948_reg_val *config;
+ size_t config_len;
const char **reg_names;
struct regulator **regs;
size_t reg_count;
@@ -212,6 +217,42 @@ static const struct regmap_config ti948_regmap_config = {
.writeable_reg = ti948_writeable_reg,
};

+static int ti948_write_sequence(
+ struct ti948_ctx *ti948,
+ const struct ti948_reg_val *sequence,
+ u32 entries)
+{
+ int i;
+
+ for (i = 0; i < entries; i++) {
+ const struct ti948_reg_val *r = sequence + i;
+ int ret = regmap_write(ti948->regmap, r->addr, r->value);
+
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ti948_write_config_seq(struct ti948_ctx *ti948)
+{
+ int ret;
+
+ if (ti948->config == NULL) {
+ dev_info(&ti948->i2c->dev, "No config for ti948 device\n");
+ return 0;
+ }
+
+ ret = ti948_write_sequence(ti948, ti948->config, ti948->config_len);
+ if (ret < 0)
+ return ret;
+
+ dev_info(&ti948->i2c->dev, "Successfully configured ti948\n");
+
+ return ret;
+}
+
static inline u8 ti948_device_address(struct ti948_ctx *ti948)
{
return ti948->i2c->addr;
@@ -345,7 +386,7 @@ static int ti948_pm_resume(struct device *dev)
if (ret != 0)
return ret;

- return 0;
+ return ti948_write_config_seq(ti948);
}

static int ti948_pm_suspend(struct device *dev)
@@ -434,6 +475,65 @@ static int ti948_get_regulators(struct ti948_ctx *ti948)
return 0;
}

+static int ti948_get_config(struct ti948_ctx *ti948)
+{
+ int i;
+ int ret;
+ u8 *config;
+ size_t config_len;
+
+ ret = device_property_read_u8_array(&ti948->i2c->dev,
+ "config", NULL, 0);
+ if (ret == -EINVAL ||
+ ret == -ENODATA ||
+ ret == 0) {
+ /* "config" property was either:
+ * - unset
+ * - valueless
+ * - set to empty list
+ * Not an error; continue without config.
+ */
+ dev_info(&ti948->i2c->dev, "No config defined for device.\n");
+ return 0;
+
+ } else if (ret < 0) {
+ return ret;
+ } else if (ret & 0x1) {
+ dev_err(&ti948->i2c->dev,
+ "Device property 'config' needs even entry count.\n");
+ return -EINVAL;
+ }
+
+ config_len = ret;
+
+ config = kmalloc_array(config_len, sizeof(*config), GFP_KERNEL);
+ if (!config)
+ return -ENOMEM;
+
+ ret = device_property_read_u8_array(&ti948->i2c->dev, "config",
+ config, config_len);
+ if (ret < 0) {
+ kfree(config);
+ return ret;
+ }
+
+ ti948->config = devm_kmalloc_array(&ti948->i2c->dev,
+ config_len / 2, sizeof(*ti948->config), GFP_KERNEL);
+ if (!ti948->config) {
+ kfree(config);
+ return -ENOMEM;
+ }
+
+ ti948->config_len = config_len / 2;
+ for (i = 0; i < config_len; i += 2) {
+ ti948->config[i / 2].addr = config[i];
+ ti948->config[i / 2].value = config[i + 1];
+ }
+ kfree(config);
+
+ return 0;
+}
+
static int ti948_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
@@ -456,6 +556,10 @@ static int ti948_probe(struct i2c_client *client,
if (ret != 0)
return ret;

+ ret = ti948_get_config(ti948);
+ if (ret != 0)
+ return ret;
+
i2c_set_clientdata(client, ti948);

ret = ti948_pm_resume(&client->dev);
--
2.20.1

2019-06-11 14:10:55

by Michael Drake

[permalink] [raw]
Subject: [PATCH v1 09/11] ti949: i2c device driver for TI DS90UB949-Q1

This is a regmap i2c device driver for:

TI DS90UB949-Q1 1080p HDMI to FPD-Link III bridge serializer

It supports instantiation via device tree / ACPI table.

A list of regulators to enable for use of the device (e.g.
GPIOs for turning on power) may be provided as device tree
properties. These are enabled on probe and PM resume.
They are disabled on remove and PM suspend.

Datasheet: http://www.ti.com/lit/ds/symlink/ds90ub949-q1.pdf

Signed-off-by: Michael Drake <[email protected]>
Cc: Patrick Glaser <[email protected]>
Cc: Nate Case <[email protected]>
---
drivers/gpu/drm/bridge/Kconfig | 8 +
drivers/gpu/drm/bridge/Makefile | 1 +
drivers/gpu/drm/bridge/ti949.c | 511 ++++++++++++++++++++++++++++++++
3 files changed, 520 insertions(+)
create mode 100644 drivers/gpu/drm/bridge/ti949.c

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 149692dc8d48..fbc0eddda8f0 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -89,6 +89,14 @@ config DRM_TI948
This is a driver for the TI DS90UB948-Q1 device.
It converts video signals from FPD-Link III to OpenLDI.

+config DRM_TI949
+ tristate "TI DS90UB949-Q1 1080p HDMI to FPD-Link III bridge serializer"
+ select DRM_KMS_HELPER
+ select REGMAP_I2C
+ help
+ This is a driver for the TI DS90UB949-Q1 device.
+ It converts video signals from HDMI to FPD-Link III.
+
config DRM_SIL_SII8620
tristate "Silicon Image SII8620 HDMI/MHL bridge"
depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index 3fb37bbe10e1..1788b5efe234 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v
obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
obj-$(CONFIG_DRM_TI948) += ti948.o
+obj-$(CONFIG_DRM_TI949) += ti949.o
obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o
obj-$(CONFIG_DRM_SII902X) += sii902x.o
obj-$(CONFIG_DRM_SII9234) += sii9234.o
diff --git a/drivers/gpu/drm/bridge/ti949.c b/drivers/gpu/drm/bridge/ti949.c
new file mode 100644
index 000000000000..04618ca5f25e
--- /dev/null
+++ b/drivers/gpu/drm/bridge/ti949.c
@@ -0,0 +1,511 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TI DS90UB949-Q1 1080p HDMI to FPD-Link III bridge serializer driver
+ *
+ * Copyright (C) 2019 Tesla Motors, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/regulator/consumer.h>
+#include <linux/of_device.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+
+/* Number of times to try checking for device on bringup. */
+#define TI949_DEVICE_ID_TRIES 10
+
+/**
+ * enum ti949_reg - TI949 registers.
+ *
+ * DS90UB949-Q1: 8.6 Register Maps: Table 10. Serial Control Bus Registers
+ *
+ * http://www.ti.com/lit/ds/snls452/snls452.pdf
+ */
+enum ti949_reg {
+ TI949_REG_I2C_DEVICE_ID = 0x00,
+ TI949_REG_RESET = 0x01,
+ TI949_REG_GENERAL_CONFIGURATION = 0x03,
+ TI949_REG_MODE_SELECT = 0x04,
+ TI949_REG_I2C_CONTROL = 0x05,
+ TI949_REG_DES_ID = 0x06,
+ TI949_REG_SLAVE_ID = 0x07,
+ TI949_REG_SLAVE_ALIAS = 0x08,
+ TI949_REG_CRC_ERRORS_A = 0x0A,
+ TI949_REG_CRC_ERRORS_B = 0x0B,
+ TI949_REG_GENERAL_STATUS = 0x0C,
+ TI949_REG_GPIO0_CONFIGURATION = 0x0D,
+ TI949_REG_GPIO1_AND_2_CONFIGURATION = 0x0E,
+ TI949_REG_GPIO3_CONFIGURATION = 0x0F,
+ TI949_REG_GPIO5_REG_AND_GPIO6_REG_CONFIGURATION = 0x10,
+ TI949_REG_GPIO7_REG_AND_GPIO8_REG_CONFIGURATION = 0x11,
+ TI949_REG_DATA_PATH_CONTROL = 0x12,
+ TI949_REG_GENERAL_PURPOSE_CONTROL = 0x13,
+ TI949_REG_BIST_CONTROL = 0x14,
+ TI949_REG_I2C_VOLTAGE_SELECT = 0x15,
+ TI949_REG_BCC_WATCHDOG_CONTROL = 0x16,
+ TI949_REG_I2C_CONTROL2 = 0x17,
+ TI949_REG_SCL_HIGH_TIME = 0x18,
+ TI949_REG_SCL_LOW_TIME = 0x19,
+ TI949_REG_DATA_PATH_CONTROL_2 = 0x1A,
+ TI949_REG_BIST_BC_ERROR_COUNT = 0x1B,
+ TI949_REG_GPIO_PIN_STATUS_1 = 0x1C,
+ TI949_REG_GPIO_PIN_STATUS_2 = 0x1D,
+ TI949_REG_TRANSMITTER_PORT_SELECT = 0x1E,
+ TI949_REG_FREQUENCY_COUNTER = 0x1F,
+ TI949_REG_DESERIALIZER_CAPABILITIES_1 = 0x20,
+ TI949_REG_DESERIALIZER_CAPABILITIES_2 = 0x21,
+ TI949_REG_LINK_DETECT_CONTROL = 0x26,
+ TI949_REG_SCLK_CTRL = 0x30,
+ TI949_REG_AUDIO_CTS0 = 0x31,
+ TI949_REG_AUDIO_CTS1 = 0x32,
+ TI949_REG_AUDIO_CTS2 = 0x33,
+ TI949_REG_AUDIO_N0 = 0x34,
+ TI949_REG_AUDIO_N1 = 0x35,
+ TI949_REG_AUDIO_N2_COEFF = 0x36,
+ TI949_REG_CLK_CLEAN_STS = 0x37,
+ TI949_REG_ANA_IA_CNTL = 0x40,
+ TI949_REG_ANA_IA_ADDR = 0x41,
+ TI949_REG_ANA_IA_CTL = 0x42,
+ TI949_REG_APB_CTL = 0x48,
+ TI949_REG_APB_ADR0 = 0x49,
+ TI949_REG_APB_ADR1 = 0x4A,
+ TI949_REG_APB_DATA0 = 0x4B,
+ TI949_REG_APB_DATA1 = 0x4C,
+ TI949_REG_APB_DATA2 = 0x4D,
+ TI949_REG_APB_DATA3 = 0x4E,
+ TI949_REG_BRIDGE_CTL = 0x4F,
+ TI949_REG_BRIDGE_STS = 0x50,
+ TI949_REG_EDID_ID = 0x51,
+ TI949_REG_EDID_CFG0 = 0x52,
+ TI949_REG_EDID_CFG1 = 0x53,
+ TI949_REG_BRIDGE_CFG = 0x54,
+ TI949_REG_AUDIO_CFG = 0x55,
+ TI949_REG_DUAL_STS = 0x5A,
+ TI949_REG_DUAL_CTL1 = 0x5B,
+ TI949_REG_DUAL_CTL2 = 0x5C,
+ TI949_REG_FREQ_LOW = 0x5D,
+ TI949_REG_FREQ_HIGH = 0x5E,
+ TI949_REG_HDMI_FREQUENCY = 0x5F,
+ TI949_REG_SPI_TIMING1 = 0x60,
+ TI949_REG_SPI_TIMING2 = 0x61,
+ TI949_REG_SPI_CONFIG = 0x62,
+ TI949_REG_PATTERN_GENERATOR_CONTROL = 0x64,
+ TI949_REG_PATTERN_GENERATOR_CONFIGURATION = 0x65,
+ TI949_REG_PGIA = 0x66,
+ TI949_REG_PGID = 0x67,
+ TI949_REG_SLAVE_ID1 = 0x70,
+ TI949_REG_SLAVE_ID2 = 0x71,
+ TI949_REG_SLAVE_ID3 = 0x72,
+ TI949_REG_SLAVE_ID4 = 0x73,
+ TI949_REG_SLAVE_ID5 = 0x74,
+ TI949_REG_SLAVE_ID6 = 0x75,
+ TI949_REG_SLAVE_ID7 = 0x76,
+ TI949_REG_SLAVE_ALIAS1 = 0x77,
+ TI949_REG_SLAVE_ALIAS2 = 0x78,
+ TI949_REG_SLAVE_ALIAS3 = 0x79,
+ TI949_REG_SLAVE_ALIAS4 = 0x7A,
+ TI949_REG_SLAVE_ALIAS5 = 0x7B,
+ TI949_REG_SLAVE_ALIAS6 = 0x7C,
+ TI949_REG_SLAVE_ALIAS7 = 0x7D,
+ TI949_REG_ICR = 0xC6,
+ TI949_REG_ISR = 0xC7,
+ TI949_REG_TX_ID_0 = 0xF0,
+ TI949_REG_TX_ID_1 = 0xF1,
+ TI949_REG_TX_ID_2 = 0xF2,
+ TI949_REG_TX_ID_3 = 0xF3,
+ TI949_REG_TX_ID_4 = 0xF4,
+ TI949_REG_TX_ID_5 = 0xF5,
+};
+
+/**
+ * struct ti949_ctx - ti949 driver context
+ * @i2c: Handle for the device's i2c client.
+ * @regmap: Handle for the device's regmap.
+ * @reg_names: Array of regulator names, or NULL.
+ * @regs: Array of regulators, or NULL.
+ * @reg_count: Number of entries in reg_names and regs arrays.
+ */
+struct ti949_ctx {
+ struct i2c_client *i2c;
+ struct regmap *regmap;
+ const char **reg_names;
+ struct regulator **regs;
+ size_t reg_count;
+};
+
+static bool ti949_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TI949_REG_I2C_DEVICE_ID ... TI949_REG_DESERIALIZER_CAPABILITIES_2:
+ /*fall through*/
+ case TI949_REG_LINK_DETECT_CONTROL:
+ /*fall through*/
+ case TI949_REG_SCLK_CTRL ... TI949_REG_CLK_CLEAN_STS:
+ /*fall through*/
+ case TI949_REG_ANA_IA_CNTL ... TI949_REG_ANA_IA_CTL:
+ /*fall through*/
+ case TI949_REG_APB_CTL ... TI949_REG_AUDIO_CFG:
+ /*fall through*/
+ case TI949_REG_DUAL_STS ... TI949_REG_SPI_CONFIG:
+ /*fall through*/
+ case TI949_REG_PATTERN_GENERATOR_CONTROL ... TI949_REG_PGID:
+ /*fall through*/
+ case TI949_REG_SLAVE_ID1 ... TI949_REG_SLAVE_ALIAS7:
+ /*fall through*/
+ case TI949_REG_ICR ... TI949_REG_ISR:
+ /*fall through*/
+ case TI949_REG_TX_ID_0 ... TI949_REG_TX_ID_5:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool ti949_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TI949_REG_I2C_DEVICE_ID ... TI949_REG_SLAVE_ALIAS:
+ /*fall through*/
+ case TI949_REG_GPIO0_CONFIGURATION ... TI949_REG_DATA_PATH_CONTROL:
+ /*fall through*/
+ case TI949_REG_BIST_CONTROL ... TI949_REG_DATA_PATH_CONTROL_2:
+ /*fall through*/
+ case TI949_REG_TRANSMITTER_PORT_SELECT ...
+ TI949_REG_DESERIALIZER_CAPABILITIES_2:
+ /*fall through*/
+ case TI949_REG_LINK_DETECT_CONTROL:
+ /*fall through*/
+ case TI949_REG_SCLK_CTRL ... TI949_REG_AUDIO_N2_COEFF:
+ /*fall through*/
+ case TI949_REG_ANA_IA_CNTL ... TI949_REG_ANA_IA_CTL:
+ /*fall through*/
+ case TI949_REG_APB_CTL ... TI949_REG_BRIDGE_CTL:
+ /*fall through*/
+ case TI949_REG_EDID_ID ... TI949_REG_AUDIO_CFG:
+ /*fall through*/
+ case TI949_REG_DUAL_CTL1 ... TI949_REG_SPI_CONFIG:
+ /*fall through*/
+ case TI949_REG_PATTERN_GENERATOR_CONTROL ... TI949_REG_PGID:
+ /*fall through*/
+ case TI949_REG_SLAVE_ID1 ... TI949_REG_SLAVE_ALIAS7:
+ /*fall through*/
+ case TI949_REG_ICR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config ti949_regmap_config = {
+ .val_bits = 8,
+ .reg_bits = 8,
+ .max_register = TI949_REG_TX_ID_5,
+ .readable_reg = ti949_readable_reg,
+ .writeable_reg = ti949_writeable_reg,
+};
+
+static inline u8 ti949_device_address(struct ti949_ctx *ti949)
+{
+ return ti949->i2c->addr;
+}
+
+static inline u8 ti949_device_expected_id(struct ti949_ctx *ti949)
+{
+ return ti949_device_address(ti949) << 1;
+}
+
+static int ti949_device_check(struct ti949_ctx *ti949)
+{
+ unsigned int i2c_device_id;
+ int ret;
+
+ ret = regmap_read(ti949->regmap, TI949_REG_I2C_DEVICE_ID,
+ &i2c_device_id);
+ if (ret < 0) {
+ dev_err(&ti949->i2c->dev,
+ "Failed to read device id. (error %d)\n", ret);
+ return -ENODEV;
+ }
+
+ if (i2c_device_id != ti949_device_expected_id(ti949)) {
+ dev_err(&ti949->i2c->dev,
+ "Bad device ID: Got: %x, Expected: %x\n",
+ i2c_device_id, ti949_device_expected_id(ti949));
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+/**
+ * ti949_device_await() - Repeatedly check the ti949's device ID.
+ * @ti949: ti949 context to check.
+ *
+ * Repeatedly check the ti949's device ID to verify that the ti949 can be
+ * communicated with, and that it is the correct version.
+ *
+ * This function can also be used to wait until the ti949 is ready, after
+ * reset.
+ *
+ * Returns: 0 on success, negative on failure.
+ */
+static int ti949_device_await(struct ti949_ctx *ti949)
+{
+ int tries = 0;
+ int ret;
+
+ do {
+ ret = ti949_device_check(ti949);
+ if (ret < 0)
+ mdelay(1);
+ else
+ break;
+ } while (++tries < TI949_DEVICE_ID_TRIES);
+
+ if (tries >= TI949_DEVICE_ID_TRIES)
+ dev_err(&ti949->i2c->dev,
+ "Failed to read id after %d attempt(s)\n",
+ tries);
+
+ return ret;
+}
+
+static int ti949_power_on(struct ti949_ctx *ti949)
+{
+ int i;
+ int ret;
+
+ for (i = 0; i < ti949->reg_count; i++) {
+ dev_info(&ti949->i2c->dev, "Enabling %s regulator\n",
+ ti949->reg_names[i]);
+ ret = regulator_enable(ti949->regs[i]);
+ if (ret < 0) {
+ dev_err(&ti949->i2c->dev,
+ "Could not enable regulator %s: %d\n",
+ ti949->reg_names[i], ret);
+ return ret;
+ }
+
+ msleep(100);
+ }
+
+ ret = ti949_device_await(ti949);
+ if (ret != 0)
+ return ret;
+
+ msleep(500);
+
+ return 0;
+}
+
+static int ti949_power_off(struct ti949_ctx *ti949)
+{
+ int i;
+ int ret;
+
+ for (i = ti949->reg_count; i > 0; i--) {
+ dev_info(&ti949->i2c->dev, "Disabling %s regulator\n",
+ ti949->reg_names[i - 1]);
+ ret = regulator_disable(ti949->regs[i - 1]);
+ if (ret < 0) {
+ dev_err(&ti949->i2c->dev,
+ "Could not disable regulator %s: %d\n",
+ ti949->reg_names[i - 1], ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static inline struct ti949_ctx *ti949_ctx_from_dev(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ return i2c_get_clientdata(client);
+}
+
+static int ti949_pm_resume(struct device *dev)
+{
+ struct ti949_ctx *ti949 = ti949_ctx_from_dev(dev);
+ int ret;
+
+ if (ti949 == NULL)
+ return 0;
+
+ ret = ti949_power_on(ti949);
+ if (ret != 0)
+ return ret;
+
+ return 0;
+}
+
+static int ti949_pm_suspend(struct device *dev)
+{
+ struct ti949_ctx *ti949 = ti949_ctx_from_dev(dev);
+
+ if (ti949 == NULL)
+ return 0;
+
+ return ti949_power_off(ti949);
+}
+
+static int ti949_get_regulator(struct device *dev, const char *id,
+ struct regulator **reg_out)
+{
+ struct regulator *reg;
+
+ reg = devm_regulator_get(dev, id);
+ if (IS_ERR(reg)) {
+ int ret = PTR_ERR(reg);
+
+ dev_err(dev, "Error requesting %s regulator: %d\n", id, ret);
+ return ret;
+ }
+
+ if (reg == NULL)
+ dev_warn(dev, "Could not get %s regulator\n", id);
+ else
+ dev_info(dev, "Got regulator %s\n", id);
+
+ *reg_out = reg;
+ return 0;
+}
+
+static int ti949_get_regulators(struct ti949_ctx *ti949)
+{
+ int i;
+ int ret;
+ size_t regulator_count;
+
+ ret = device_property_read_string_array(&ti949->i2c->dev,
+ "regulators", NULL, 0);
+ if (ret == -EINVAL ||
+ ret == -ENODATA ||
+ ret == 0) {
+ /* "regulators" property was either:
+ * - unset
+ * - valueless
+ * - set to empty list
+ * Not an error; continue without regulators.
+ */
+ dev_info(&ti949->i2c->dev,
+ "No regulators listed for device.\n");
+ return 0;
+
+ } else if (ret < 0) {
+ return ret;
+ }
+
+ regulator_count = ret;
+
+ ti949->reg_names = devm_kmalloc_array(&ti949->i2c->dev,
+ regulator_count, sizeof(*ti949->reg_names), GFP_KERNEL);
+ if (!ti949->reg_names)
+ return -ENOMEM;
+
+ ret = device_property_read_string_array(&ti949->i2c->dev, "regulators",
+ ti949->reg_names, regulator_count);
+ if (ret < 0)
+ return ret;
+
+ ti949->regs = devm_kmalloc_array(&ti949->i2c->dev,
+ regulator_count, sizeof(*ti949->regs), GFP_KERNEL);
+ if (!ti949->regs)
+ return -ENOMEM;
+
+ for (i = 0; i < regulator_count; i++) {
+ ret = ti949_get_regulator(&ti949->i2c->dev,
+ ti949->reg_names[i], &ti949->regs[i]);
+ if (ret != 0)
+ return ret;
+ }
+
+ ti949->reg_count = regulator_count;
+
+ return 0;
+}
+
+static int ti949_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ti949_ctx *ti949;
+ int ret;
+
+ dev_info(&client->dev, "Begin probe (addr: %x)\n", client->addr);
+
+ ti949 = devm_kzalloc(&client->dev, sizeof(*ti949), GFP_KERNEL);
+ if (!ti949)
+ return -ENOMEM;
+
+ ti949->i2c = client;
+
+ ti949->regmap = devm_regmap_init_i2c(ti949->i2c, &ti949_regmap_config);
+ if (IS_ERR(ti949->regmap))
+ return PTR_ERR(ti949->regmap);
+
+ ret = ti949_get_regulators(ti949);
+ if (ret != 0)
+ return ret;
+
+ i2c_set_clientdata(client, ti949);
+
+ ret = ti949_pm_resume(&client->dev);
+ if (ret != 0)
+ return -EPROBE_DEFER;
+
+ dev_info(&ti949->i2c->dev, "End probe (addr: %x)\n", ti949->i2c->addr);
+
+ return 0;
+}
+
+static int ti949_remove(struct i2c_client *client)
+{
+ return ti949_pm_suspend(&client->dev);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id ti949_of_match[] = {
+ { .compatible = "ti,ds90ub949" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ti949_of_match);
+#endif
+
+static const struct i2c_device_id ti949_i2c_id[] = {
+ {"ti949", 0},
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, ti949_i2c_id);
+
+static const struct dev_pm_ops ti949_pm_ops = {
+ .resume = ti949_pm_resume,
+ .suspend = ti949_pm_suspend,
+};
+
+static struct i2c_driver ti949_driver = {
+ .driver = {
+ .name = "ti949",
+ .owner = THIS_MODULE,
+ .pm = &ti949_pm_ops,
+ .of_match_table = of_match_ptr(ti949_of_match),
+ },
+ .id_table = ti949_i2c_id,
+ .probe = ti949_probe,
+ .remove = ti949_remove,
+};
+module_i2c_driver(ti949_driver);
+
+MODULE_AUTHOR("Michael Drake <[email protected]");
+MODULE_DESCRIPTION("TI DS90UB949-Q1 1080p HDMI to FPD-Link III bridge serializer driver");
+MODULE_LICENSE("GPL v2");
--
2.20.1

2019-06-11 14:10:56

by Michael Drake

[permalink] [raw]
Subject: [PATCH v1 05/11] ti948: Add alive check function using schedule_delayed_work()

This simply runs the function once every 5 seconds, while the
device is supposed to be active. The alive check function is
currently simply a stub, that logs it has been called, and
re-inserts itself into the work queue.

Signed-off-by: Michael Drake <[email protected]>
Cc: Patrick Glaser <[email protected]>
Cc: Nate Case <[email protected]>
---
drivers/gpu/drm/bridge/ti948.c | 37 +++++++++++++++++++++++++++++++++-
1 file changed, 36 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/bridge/ti948.c b/drivers/gpu/drm/bridge/ti948.c
index 9cb37215f049..86daa3701b91 100644
--- a/drivers/gpu/drm/bridge/ti948.c
+++ b/drivers/gpu/drm/bridge/ti948.c
@@ -16,6 +16,7 @@

#include <linux/regulator/consumer.h>
#include <linux/of_device.h>
+#include <linux/workqueue.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/delay.h>
@@ -25,6 +26,9 @@
/* Number of times to try checking for device on bringup. */
#define TI948_DEVICE_ID_TRIES 10

+/* Alive check every 5 seconds. */
+#define TI948_ALIVE_CHECK_DELAY (5 * HZ)
+
/**
* enum ti948_reg - TI948 registers.
*
@@ -374,9 +378,27 @@ static inline struct ti948_ctx *ti948_ctx_from_dev(struct device *dev)
return i2c_get_clientdata(client);
}

+static inline struct ti948_ctx *delayed_work_to_ti948_ctx(
+ struct delayed_work *dwork)
+{
+ return container_of(dwork, struct ti948_ctx, alive_check);
+}
+
+static void ti948_alive_check(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct ti948_ctx *ti948 = delayed_work_to_ti948_ctx(dwork);
+
+ dev_info(&ti948->i2c->dev, "%s Alive check!\n", __func__);
+
+ /* Reschedule ourself for the next check. */
+ schedule_delayed_work(&ti948->alive_check, TI948_ALIVE_CHECK_DELAY);
+}
+
static int ti948_pm_resume(struct device *dev)
{
struct ti948_ctx *ti948 = ti948_ctx_from_dev(dev);
+ bool scheduled;
int ret;

if (ti948 == NULL)
@@ -386,7 +408,18 @@ static int ti948_pm_resume(struct device *dev)
if (ret != 0)
return ret;

- return ti948_write_config_seq(ti948);
+ ret = ti948_write_config_seq(ti948);
+ if (ret != 0)
+ return ret;
+
+ INIT_DELAYED_WORK(&ti948->alive_check, ti948_alive_check);
+
+ scheduled = schedule_delayed_work(
+ &ti948->alive_check, TI948_ALIVE_CHECK_DELAY);
+ if (!scheduled)
+ dev_warn(&ti948->i2c->dev, "Alive check already scheduled\n");
+
+ return 0;
}

static int ti948_pm_suspend(struct device *dev)
@@ -396,6 +429,8 @@ static int ti948_pm_suspend(struct device *dev)
if (ti948 == NULL)
return 0;

+ cancel_delayed_work_sync(&ti948->alive_check);
+
return ti948_power_off(ti948);
}

--
2.20.1

2019-06-11 14:10:58

by Michael Drake

[permalink] [raw]
Subject: [PATCH v1 02/11] ti948: i2c device driver for TI DS90UB948-Q1

This is a regmap i2c device driver for:

TI DS90UB948-Q1 2K FPD-Link III to OpenLDI Deserializer

It supports instantiation via device tree / ACPI table.

A list of regulators to enable for use of the device (e.g.
GPIOs for turning on power) may be provided as device tree
properties. These are enabled on probe and PM resume.
They are disabled on remove and PM suspend.

Datasheet: http://www.ti.com/lit/ds/symlink/ds90ub948-q1.pdf

Signed-off-by: Michael Drake <[email protected]>
Cc: Patrick Glaser <[email protected]>
Cc: Nate Case <[email protected]>
---
drivers/gpu/drm/bridge/Kconfig | 8 +
drivers/gpu/drm/bridge/Makefile | 1 +
drivers/gpu/drm/bridge/ti948.c | 509 ++++++++++++++++++++++++++++++++
3 files changed, 518 insertions(+)
create mode 100644 drivers/gpu/drm/bridge/ti948.c

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index ee777469293a..149692dc8d48 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -81,6 +81,14 @@ config DRM_PARADE_PS8622
---help---
Parade eDP-LVDS bridge chip driver.

+config DRM_TI948
+ tristate "TI DS90UB948-Q1 FPD-Link III to OpenLDI Deserializer"
+ select DRM_KMS_HELPER
+ select REGMAP_I2C
+ help
+ This is a driver for the TI DS90UB948-Q1 device.
+ It converts video signals from FPD-Link III to OpenLDI.
+
config DRM_SIL_SII8620
tristate "Silicon Image SII8620 HDMI/MHL bridge"
depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index 4934fcf5a6f8..3fb37bbe10e1 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_DRM_LVDS_ENCODER) += lvds-encoder.o
obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
+obj-$(CONFIG_DRM_TI948) += ti948.o
obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o
obj-$(CONFIG_DRM_SII902X) += sii902x.o
obj-$(CONFIG_DRM_SII9234) += sii9234.o
diff --git a/drivers/gpu/drm/bridge/ti948.c b/drivers/gpu/drm/bridge/ti948.c
new file mode 100644
index 000000000000..c22252036bbe
--- /dev/null
+++ b/drivers/gpu/drm/bridge/ti948.c
@@ -0,0 +1,509 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TI DS90UB948-Q1 2K FPD-Link III to OpenLDI Deserializer driver
+ *
+ * Copyright (C) 2019 Tesla Motors, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/regulator/consumer.h>
+#include <linux/of_device.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+
+/* Number of times to try checking for device on bringup. */
+#define TI948_DEVICE_ID_TRIES 10
+
+/**
+ * enum ti948_reg - TI948 registers.
+ *
+ * DS90UB948-Q1: 7.7: Table 11 DS90UB948-Q1 Registers
+ *
+ * http://www.ti.com/lit/ds/symlink/ds90ub948-q1.pdf
+ */
+enum ti948_reg {
+ TI948_REG_I2C_DEVICE_ID = 0x00,
+ TI948_REG_RESET = 0x01,
+ TI948_REG_GENERAL_CONFIGURATION_0 = 0x02,
+ TI948_REG_GENERAL_CONFIGURATION_1 = 0x03,
+ TI948_REG_BCC_WATCHDOG_CONTROL = 0x04,
+ TI948_REG_I2C_CONTROL_1 = 0x05,
+ TI948_REG_I2C_CONTROL_2 = 0x06,
+ TI948_REG_REMOTE_ID = 0x07,
+ TI948_REG_SLAVEID_0 = 0x08,
+ TI948_REG_SLAVEID_1 = 0x09,
+ TI948_REG_SLAVEID_2 = 0x0A,
+ TI948_REG_SLAVEID_3 = 0x0B,
+ TI948_REG_SLAVEID_4 = 0x0C,
+ TI948_REG_SLAVEID_5 = 0x0D,
+ TI948_REG_SLAVEID_6 = 0x0E,
+ TI948_REG_SLAVEID_7 = 0x0F,
+ TI948_REG_SLAVEALIAS_0 = 0x10,
+ TI948_REG_SLAVEALIAS_1 = 0x11,
+ TI948_REG_SLAVEALIAS_2 = 0x12,
+ TI948_REG_SLAVEALIAS_3 = 0x13,
+ TI948_REG_SLAVEALIAS_4 = 0x14,
+ TI948_REG_SLAVEALIAS_5 = 0x15,
+ TI948_REG_SLAVEALIAS_6 = 0x16,
+ TI948_REG_SLAVEALIAS_7 = 0x17,
+ TI948_REG_MAILBOX_18 = 0x18,
+ TI948_REG_MAILBOX_19 = 0x19,
+ TI948_REG_GPIO_9_AND_GLOBAL_GPIO_CONFIG = 0x1A,
+ TI948_REG_FREQUENCY_COUNTER = 0x1B,
+ TI948_REG_GENERAL_STATUS = 0x1C,
+ TI948_REG_GPIO0_CONFIG = 0x1D,
+ TI948_REG_GPIO1_2_CONFIG = 0x1E,
+ TI948_REG_GPIO3_CONFIG = 0x1F,
+ TI948_REG_GPIO5_6_CONFIG = 0x20,
+ TI948_REG_GPIO7_8_CONFIG = 0x21,
+ TI948_REG_DATAPATH_CONTROL = 0x22,
+ TI948_REG_RX_MODE_STATUS = 0x23,
+ TI948_REG_BIST_CONTROL = 0x24,
+ TI948_REG_BIST_ERROR_COUNT = 0x25,
+ TI948_REG_SCL_HIGH_TIME = 0x26,
+ TI948_REG_SCL_LOW_TIME = 0x27,
+ TI948_REG_DATAPATH_CONTROL_2 = 0x28,
+ TI948_REG_FRC_CONTROL = 0x29,
+ TI948_REG_WHITE_BALANCE_CONTROL = 0x2A,
+ TI948_REG_I2S_CONTROL = 0x2B,
+ TI948_REG_PCLK_TEST_MODE = 0x2E,
+ TI948_REG_DUAL_RX_CTL = 0x34,
+ TI948_REG_AEQ_TEST = 0x35,
+ TI948_REG_MODE_SEL = 0x37,
+ TI948_REG_I2S_DIVSEL = 0x3A,
+ TI948_REG_EQ_STATUS = 0x3B,
+ TI948_REG_LINK_ERROR_COUNT = 0x41,
+ TI948_REG_HSCC_CONTROL = 0x43,
+ TI948_REG_ADAPTIVE_EQ_BYPASS = 0x44,
+ TI948_REG_ADAPTIVE_EQ_MIN_MAX = 0x45,
+ TI948_REG_FPD_TX_MODE = 0x49,
+ TI948_REG_LVDS_CONTROL = 0x4B,
+ TI948_REG_CML_OUTPUT_CTL1 = 0x52,
+ TI948_REG_CML_OUTPUT_ENABLE = 0x56,
+ TI948_REG_CML_OUTPUT_CTL2 = 0x57,
+ TI948_REG_CML_OUTPUT_CTL3 = 0x63,
+ TI948_REG_PGCTL = 0x64,
+ TI948_REG_PGCFG = 0x65,
+ TI948_REG_PGIA = 0x66,
+ TI948_REG_PGID = 0x67,
+ TI948_REG_PGDBG = 0x68,
+ TI948_REG_PGTSTDAT = 0x69,
+ TI948_REG_GPI_PIN_STATUS_1 = 0x6E,
+ TI948_REG_GPI_PIN_STATUS_2 = 0x6F,
+ TI948_REG_RX_ID0 = 0xF0,
+ TI948_REG_RX_ID1 = 0xF1,
+ TI948_REG_RX_ID2 = 0xF2,
+ TI948_REG_RX_ID3 = 0xF3,
+ TI948_REG_RX_ID4 = 0xF4,
+ TI948_REG_RX_ID5 = 0xF5
+};
+
+/**
+ * struct ti948_reg_val - ti948 register value
+ * @addr: The address of the register
+ * @value: The initial value of the register
+ */
+struct ti948_reg_val {
+ u8 addr;
+ u8 value;
+};
+
+/**
+ * struct ti948_ctx - ti948 driver context
+ * @i2c: Handle for the device's i2c client.
+ * @regmap: Handle for the device's regmap.
+ * @reg_names: Array of regulator names, or NULL.
+ * @regs: Array of regulators, or NULL.
+ * @reg_count: Number of entries in reg_names and regs arrays.
+ */
+struct ti948_ctx {
+ struct i2c_client *i2c;
+ struct regmap *regmap;
+ const char **reg_names;
+ struct regulator **regs;
+ size_t reg_count;
+};
+
+static bool ti948_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TI948_REG_I2C_DEVICE_ID ... TI948_REG_I2S_CONTROL:
+ /*fall through*/
+ case TI948_REG_PCLK_TEST_MODE:
+ /*fall through*/
+ case TI948_REG_DUAL_RX_CTL ... TI948_REG_AEQ_TEST:
+ /*fall through*/
+ case TI948_REG_MODE_SEL:
+ /*fall through*/
+ case TI948_REG_I2S_DIVSEL ... TI948_REG_EQ_STATUS:
+ /*fall through*/
+ case TI948_REG_LINK_ERROR_COUNT:
+ /*fall through*/
+ case TI948_REG_HSCC_CONTROL ... TI948_REG_ADAPTIVE_EQ_MIN_MAX:
+ /*fall through*/
+ case TI948_REG_FPD_TX_MODE:
+ /*fall through*/
+ case TI948_REG_LVDS_CONTROL:
+ /*fall through*/
+ case TI948_REG_CML_OUTPUT_CTL1:
+ /*fall through*/
+ case TI948_REG_CML_OUTPUT_ENABLE ... TI948_REG_CML_OUTPUT_CTL2:
+ /*fall through*/
+ case TI948_REG_CML_OUTPUT_CTL3 ... TI948_REG_PGTSTDAT:
+ /*fall through*/
+ case TI948_REG_GPI_PIN_STATUS_1 ... TI948_REG_GPI_PIN_STATUS_2:
+ /*fall through*/
+ case TI948_REG_RX_ID0 ... TI948_REG_RX_ID5:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool ti948_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TI948_REG_I2C_DEVICE_ID ... TI948_REG_FREQUENCY_COUNTER:
+ /*fall through*/
+ case TI948_REG_GPIO0_CONFIG ... TI948_REG_BIST_CONTROL:
+ /*fall through*/
+ case TI948_REG_SCL_HIGH_TIME ... TI948_REG_I2S_CONTROL:
+ /*fall through*/
+ case TI948_REG_PCLK_TEST_MODE:
+ /*fall through*/
+ case TI948_REG_DUAL_RX_CTL ... TI948_REG_AEQ_TEST:
+ /*fall through*/
+ case TI948_REG_I2S_DIVSEL:
+ /*fall through*/
+ case TI948_REG_LINK_ERROR_COUNT:
+ /*fall through*/
+ case TI948_REG_HSCC_CONTROL ... TI948_REG_ADAPTIVE_EQ_MIN_MAX:
+ /*fall through*/
+ case TI948_REG_FPD_TX_MODE:
+ /*fall through*/
+ case TI948_REG_LVDS_CONTROL:
+ /*fall through*/
+ case TI948_REG_CML_OUTPUT_CTL1:
+ /*fall through*/
+ case TI948_REG_CML_OUTPUT_ENABLE ... TI948_REG_CML_OUTPUT_CTL2:
+ /*fall through*/
+ case TI948_REG_CML_OUTPUT_CTL3 ... TI948_REG_PGDBG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config ti948_regmap_config = {
+ .val_bits = 8,
+ .reg_bits = 8,
+ .max_register = TI948_REG_RX_ID5,
+ .readable_reg = ti948_readable_reg,
+ .writeable_reg = ti948_writeable_reg,
+};
+
+static inline u8 ti948_device_address(struct ti948_ctx *ti948)
+{
+ return ti948->i2c->addr;
+}
+
+static inline u8 ti948_device_expected_id(struct ti948_ctx *ti948)
+{
+ return ti948_device_address(ti948) << 1;
+}
+
+static int ti948_device_check(struct ti948_ctx *ti948)
+{
+ unsigned int i2c_device_id;
+ int ret;
+
+ ret = regmap_read(ti948->regmap, TI948_REG_I2C_DEVICE_ID,
+ &i2c_device_id);
+ if (ret < 0) {
+ dev_err(&ti948->i2c->dev,
+ "Failed to read device id. (error %d)\n", ret);
+ return -ENODEV;
+ }
+
+ if (i2c_device_id != ti948_device_expected_id(ti948)) {
+ dev_err(&ti948->i2c->dev,
+ "Bad device ID: Got: %x, Expected: %x\n",
+ i2c_device_id, ti948_device_expected_id(ti948));
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+/**
+ * ti948_device_await() - Repeatedly check the ti948's device ID.
+ * @ti948: ti948 context to check.
+ *
+ * Repeatedly check the ti948's device ID to verify that the ti948 can be
+ * communicated with, and that it is the correct version.
+ *
+ * This function can also be used to wait until the ti948 is ready, after
+ * reset.
+ *
+ * Returns: 0 on success, negative on failure.
+ */
+static int ti948_device_await(struct ti948_ctx *ti948)
+{
+ int tries = 0;
+ int ret;
+
+ do {
+ ret = ti948_device_check(ti948);
+ if (ret < 0)
+ mdelay(1);
+ else
+ break;
+ } while (++tries < TI948_DEVICE_ID_TRIES);
+
+ if (tries >= TI948_DEVICE_ID_TRIES)
+ dev_err(&ti948->i2c->dev,
+ "Failed to read id after %d attempt(s)\n",
+ tries);
+
+ return ret;
+}
+
+static int ti948_power_on(struct ti948_ctx *ti948)
+{
+ int i;
+ int ret;
+
+ for (i = 0; i < ti948->reg_count; i++) {
+ dev_info(&ti948->i2c->dev, "Enabling %s regulator\n",
+ ti948->reg_names[i]);
+ ret = regulator_enable(ti948->regs[i]);
+ if (ret < 0) {
+ dev_err(&ti948->i2c->dev,
+ "Could not enable regulator %s: %d\n",
+ ti948->reg_names[i], ret);
+ return ret;
+ }
+
+ msleep(100);
+ }
+
+ ret = ti948_device_await(ti948);
+ if (ret != 0)
+ return ret;
+
+ msleep(500);
+
+ return 0;
+}
+
+static int ti948_power_off(struct ti948_ctx *ti948)
+{
+ int i;
+ int ret;
+
+ for (i = ti948->reg_count; i > 0; i--) {
+ dev_info(&ti948->i2c->dev, "Disabling %s regulator\n",
+ ti948->reg_names[i - 1]);
+ ret = regulator_disable(ti948->regs[i - 1]);
+ if (ret < 0) {
+ dev_err(&ti948->i2c->dev,
+ "Could not disable regulator %s: %d\n",
+ ti948->reg_names[i - 1], ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static inline struct ti948_ctx *ti948_ctx_from_dev(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ return i2c_get_clientdata(client);
+}
+
+static int ti948_pm_resume(struct device *dev)
+{
+ struct ti948_ctx *ti948 = ti948_ctx_from_dev(dev);
+ int ret;
+
+ if (ti948 == NULL)
+ return 0;
+
+ ret = ti948_power_on(ti948);
+ if (ret != 0)
+ return ret;
+
+ return 0;
+}
+
+static int ti948_pm_suspend(struct device *dev)
+{
+ struct ti948_ctx *ti948 = ti948_ctx_from_dev(dev);
+
+ if (ti948 == NULL)
+ return 0;
+
+ return ti948_power_off(ti948);
+}
+
+static int ti948_get_regulator(struct device *dev, const char *id,
+ struct regulator **reg_out)
+{
+ struct regulator *reg;
+
+ reg = devm_regulator_get(dev, id);
+ if (IS_ERR(reg)) {
+ int ret = PTR_ERR(reg);
+
+ dev_err(dev, "Error requesting %s regulator: %d\n", id, ret);
+ return ret;
+ }
+
+ if (reg == NULL)
+ dev_warn(dev, "Could not get %s regulator\n", id);
+ else
+ dev_info(dev, "Got regulator %s\n", id);
+
+ *reg_out = reg;
+ return 0;
+}
+
+static int ti948_get_regulators(struct ti948_ctx *ti948)
+{
+ int i;
+ int ret;
+ size_t regulator_count;
+
+ ret = device_property_read_string_array(&ti948->i2c->dev,
+ "regulators", NULL, 0);
+ if (ret == -EINVAL ||
+ ret == -ENODATA ||
+ ret == 0) {
+ /* "regulators" property was either:
+ * - unset
+ * - valueless
+ * - set to empty list
+ * Not an error; continue without regulators.
+ */
+ dev_info(&ti948->i2c->dev,
+ "No regulators listed for device.\n");
+ return 0;
+
+ } else if (ret < 0) {
+ return ret;
+ }
+
+ regulator_count = ret;
+
+ ti948->reg_names = devm_kmalloc_array(&ti948->i2c->dev,
+ regulator_count, sizeof(*ti948->reg_names), GFP_KERNEL);
+ if (!ti948->reg_names)
+ return -ENOMEM;
+
+ ret = device_property_read_string_array(&ti948->i2c->dev, "regulators",
+ ti948->reg_names, regulator_count);
+ if (ret < 0)
+ return ret;
+
+ ti948->regs = devm_kmalloc_array(&ti948->i2c->dev,
+ regulator_count, sizeof(*ti948->regs), GFP_KERNEL);
+ if (!ti948->regs)
+ return -ENOMEM;
+
+ for (i = 0; i < regulator_count; i++) {
+ ret = ti948_get_regulator(&ti948->i2c->dev,
+ ti948->reg_names[i], &ti948->regs[i]);
+ if (ret != 0)
+ return ret;
+ }
+
+ ti948->reg_count = regulator_count;
+
+ return 0;
+}
+
+static int ti948_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ti948_ctx *ti948;
+ int ret;
+
+ dev_info(&client->dev, "Begin probe (addr: %x)\n", client->addr);
+
+ ti948 = devm_kzalloc(&client->dev, sizeof(*ti948), GFP_KERNEL);
+ if (!ti948)
+ return -ENOMEM;
+
+ ti948->i2c = client;
+
+ ti948->regmap = devm_regmap_init_i2c(ti948->i2c, &ti948_regmap_config);
+ if (IS_ERR(ti948->regmap))
+ return PTR_ERR(ti948->regmap);
+
+ ret = ti948_get_regulators(ti948);
+ if (ret != 0)
+ return ret;
+
+ i2c_set_clientdata(client, ti948);
+
+ ret = ti948_pm_resume(&client->dev);
+ if (ret != 0)
+ return -EPROBE_DEFER;
+
+ dev_info(&ti948->i2c->dev, "End probe (addr: %x)\n", ti948->i2c->addr);
+
+ return 0;
+}
+
+static int ti948_remove(struct i2c_client *client)
+{
+ return ti948_pm_suspend(&client->dev);
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id ti948_of_match[] = {
+ { .compatible = "ti,ds90ub948" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ti948_of_match);
+#endif
+
+static const struct i2c_device_id ti948_i2c_id[] = {
+ {"ti948", 0},
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, ti948_i2c_id);
+
+static const struct dev_pm_ops ti948_pm_ops = {
+ .resume = ti948_pm_resume,
+ .suspend = ti948_pm_suspend,
+};
+
+static struct i2c_driver ti948_driver = {
+ .driver = {
+ .name = "ti948",
+ .owner = THIS_MODULE,
+ .pm = &ti948_pm_ops,
+ .of_match_table = of_match_ptr(ti948_of_match),
+ },
+ .id_table = ti948_i2c_id,
+ .probe = ti948_probe,
+ .remove = ti948_remove,
+};
+module_i2c_driver(ti948_driver);
+
+MODULE_AUTHOR("Michael Drake <[email protected]");
+MODULE_DESCRIPTION("TI DS90UB948-Q1 2K FPD-Link III to OpenLDI Deserializer driver");
+MODULE_LICENSE("GPL v2");
--
2.20.1