summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastian Reichel <sre@kernel.org>2017-02-12 01:18:08 +0100
committerSebastian Reichel <sre@kernel.org>2017-02-28 08:32:08 +0100
commitcea17575cf74671f09440e5e58f4da96a2952a14 (patch)
treea05c773cf4f93ac1f5aaf7ead91fa4bfb36014de
parentc470abd4fde40ea6a0846a2beab642a578c0b8cd (diff)
downloadlinux-cea17575cf74671f09440e5e58f4da96a2952a14.tar.bz2
i2c: add sc18is600 driver
This adds an I²C master driver for SPI -> I²C bus bridge chips. It currently supports NXP's SC18IS600 and SC18IS601, as well as Silicon Labs' CP2120. The driver was only tested on SC18IS600. TODO: - test runtime PM stuff - test clock stretching - proper platform_data for gpio subdevice Signed-off-By: Sebastian Reichel <sre@kernel.org>
-rw-r--r--Documentation/devicetree/bindings/i2c/i2c-cp2120.txt1
-rw-r--r--Documentation/devicetree/bindings/i2c/i2c-sc18is600.txt45
-rw-r--r--drivers/i2c/busses/Kconfig10
-rw-r--r--drivers/i2c/busses/Makefile1
-rw-r--r--drivers/i2c/busses/i2c-sc18is600.c690
5 files changed, 747 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/i2c/i2c-cp2120.txt b/Documentation/devicetree/bindings/i2c/i2c-cp2120.txt
new file mode 100644
index 000000000000..95e06e74f288
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-cp2120.txt
@@ -0,0 +1 @@
+Please see binding for i2c-sc18is600
diff --git a/Documentation/devicetree/bindings/i2c/i2c-sc18is600.txt b/Documentation/devicetree/bindings/i2c/i2c-sc18is600.txt
new file mode 100644
index 000000000000..d6d77061ca22
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-sc18is600.txt
@@ -0,0 +1,45 @@
+NXP SC18IS600 and Silabs CP2120 - SPI to I2C bus bridge
+
+Required properties:
+ - compatible: Should contain one of
+ * "nxp,sc18is600"
+ * "nxp,sc18is601"
+ * "silabs,cp2120"
+ - reg: address of the chip on SPI bus
+ - interrupts: Interrupt specifier. Refer to interrupt bindings.
+ - #address-cells: Should be 1.
+ - #size-cells: Should be 0.
+
+Required properties for sc18is601:
+ - clkin: Clock specifier for CLKIN pin
+
+Optional properties:
+ - clock-frequency:
+ Desired I2C bus frequency in Hz, otherwise defaults to 100000
+ - wakeup-gpios (sc18is60x only):
+ GPIO specifier for SC18IS600's WAKEUP/IO4 pin, which can be used
+ to wakeup the sc18is600 from power down mode. It's active low.
+ - reset-gpios
+ GPIO specifier for reset pin, which is active low.
+ - vdd-supply
+ Regulator specifier for VDD supply.
+ - Child nodes conforming to i2c bus binding
+
+Example:
+
+&spi_controller {
+ sc18is600: i2c@0 {
+ compatible = "nxp,sc18is600";
+ clock-frequency = <100000>;
+ reg = <0>;
+
+ vdd-supply = <&regulator_v33>;
+ wakeup-gpios = <&gpio_controller 0 GPIO_ACTIVE_LOW>;
+ reset-gpios = <&gpio_controller 1 GPIO_ACTIVE_LOW>;
+
+ i2c_device@42 {
+ compatible = "some,i2c-device";
+ reg = <0x42>;
+ };
+ };
+}
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 0cdc8443deab..86a1d8f5daba 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -209,6 +209,16 @@ config I2C_NFORCE2_S4985
This driver can also be built as a module. If so, the module
will be called i2c-nforce2-s4985.
+config I2C_SC18IS600
+ tristate "NXP SC18IS600"
+ depends on SPI && REGMAP
+ help
+ If you say yes to this option, support will be included for the
+ NXP SC18IS600 SPI to I2C-bus interface.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-sc18is600.
+
config I2C_SIS5595
tristate "SiS 5595"
depends on PCI
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 1c1bac87a9db..e199de751f58 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_I2C_ISMT) += i2c-ismt.o
obj-$(CONFIG_I2C_NFORCE2) += i2c-nforce2.o
obj-$(CONFIG_I2C_NFORCE2_S4985) += i2c-nforce2-s4985.o
obj-$(CONFIG_I2C_PIIX4) += i2c-piix4.o
+obj-$(CONFIG_I2C_SC18IS600) += i2c-sc18is600.o
obj-$(CONFIG_I2C_SIS5595) += i2c-sis5595.o
obj-$(CONFIG_I2C_SIS630) += i2c-sis630.o
obj-$(CONFIG_I2C_SIS96X) += i2c-sis96x.o
diff --git a/drivers/i2c/busses/i2c-sc18is600.c b/drivers/i2c/busses/i2c-sc18is600.c
new file mode 100644
index 000000000000..0191f88c14ef
--- /dev/null
+++ b/drivers/i2c/busses/i2c-sc18is600.c
@@ -0,0 +1,690 @@
+/*
+ * NXP SC18IS600 SPI to I2C bus interface driver
+ *
+ * Copyright (C) 2017 Sebastian Reichel <sre@kernel.org>
+ *
+ * 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.
+ *
+ * Datasheets:
+ * - http://www.nxp.com/documents/data_sheet/SC18IS600.pdf
+ * - https://www.silabs.com/documents/public/data-sheets/CP2120.pdf
+ */
+
+#define DEBUG
+
+#include <linux/clk.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+#define SC18IS600_I2C_PM_TIMEOUT 1000 /* ms */
+#define SC18IS600_DEFAULT_FREQ 100000
+
+#define SC18IS600_CMD_WR 0x00 /* write */
+#define SC18IS600_CMD_RD 0x01 /* read */
+#define SC18IS600_CMD_WR_RD 0x02 /* read after write */
+#define SC18IS600_CMD_WR_WR 0x03 /* write after write */
+#define SC18IS600_CMD_RDBUF 0x06 /* read buffer */
+#define CP2120_CMD_WRMULTI 0x09 /* write to multiple slaves */
+#define SC18IS600_CMD_SPICON 0x18 /* spi endianess configuration */
+#define SC18IS600_CMD_REG_WR 0x20 /* write register */
+#define SC18IS600_CMD_REG_RD 0x21 /* read register */
+#define SC18IS600_CMD_PWRDWN 0x30 /* power down */
+#define CP2120_CMD_REVISION 0x40 /* read revision */
+
+#define SC18IS600_REG_IO_CONFIG 0x00
+#define SC18IS600_REG_IO_STATE 0x01
+#define SC18IS600_REG_I2C_CLOCK 0x02
+#define SC18IS600_REG_I2C_TIMEOUT 0x03
+#define SC18IS600_REG_I2C_STAT 0x04
+#define SC18IS600_REG_I2C_ADDR 0x05
+#define SC18IS600_REG_I2C_BUFFER 0x06 /* only cp2120 */
+#define SC18IS600_REG_IO_CONFIG2 0x07 /* only cp2120 */
+#define SC18IS600_REG_EDGEINT 0x08 /* only cp2120 */
+#define SC18IS600_REG_I2C_TIMEOUT2 0x09 /* only cp2120 */
+
+#define SC18IS600_STAT_OK 0xF0
+#define SC18IS600_STAT_NAK_ADDR 0xF1
+#define SC18IS600_STAT_NAK_DATA 0xF2
+#define SC18IS600_STAT_BUSY 0xF3
+#define SC18IS600_STAT_TIMEOUT 0xF8
+#define SC18IS600_STAT_SIZE 0xF9
+#define SC18IS600_STAT_TIMEOUT2 0xFA /* only cp2120 */
+#define SC18IS600_STAT_BLOCKED 0xFB /* only cp2120 */
+
+#define CMD_BUFFER_SIZE 5
+
+enum chiptype {
+ SPI2I2C_SC18IS600,
+ SPI2I2C_SC18IS601,
+ SPI2I2C_CP2120,
+};
+
+struct chipdesc {
+ u8 type;
+ u32 max_spi_speed;
+ u32 buffer_size;
+ u32 clock_base;
+ u32 timeout_base;
+ const struct regmap_config *regmap_cfg;
+};
+
+static bool sc18is600_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case SC18IS600_REG_I2C_STAT:
+ case SC18IS600_REG_I2C_BUFFER:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static const struct regmap_config sc18is600_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = 0x05,
+ .writeable_reg = sc18is600_writeable_reg,
+};
+
+static const struct regmap_config cp2120_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = 0x09,
+ .writeable_reg = sc18is600_writeable_reg,
+};
+
+/*
+ * Note: The sc18is600's datasheet promotes 1.2MHz SPI support, but my chip did
+ * not behave correctly at that speed. It seems to receive the bytes correctly,
+ * but interpret them incorrectly. At 800 KHz I still got a few errors (< 1%)
+ * and at 700 KHz everything seems to work smoothly.
+ */
+static const struct chipdesc chip_sc18is600 = {
+ .type = SPI2I2C_SC18IS600,
+ .max_spi_speed = 700000,
+ .buffer_size = 96,
+ .clock_base = 1843200,
+ .buffer_size = 96,
+ .clock_base = 1843200,
+ .timeout_base = 1125, /* 112.5 Hz */
+ .regmap_cfg = &sc18is600_regmap_config,
+};
+
+static const struct chipdesc chip_sc18is601 = {
+ .type = SPI2I2C_SC18IS601,
+ .max_spi_speed = 3000000,
+ .buffer_size = 96,
+ .clock_base = 0,
+ .timeout_base = 1125, /* 112.5 Hz */
+ .regmap_cfg = &sc18is600_regmap_config,
+};
+
+static const struct chipdesc chip_cp2120 = {
+ .type = SPI2I2C_CP2120,
+ .max_spi_speed = 1000000,
+ .buffer_size = 255,
+ .clock_base = 2000000,
+ .timeout_base = 1280, /* 128 Hz */
+ .regmap_cfg = &cp2120_regmap_config,
+};
+
+struct sc18is600dev {
+ struct i2c_adapter adapter;
+ struct completion completion;
+ struct spi_device *spi;
+ struct regmap *regmap;
+ const struct chipdesc *chip;
+ struct gpio_desc *reset;
+ struct gpio_desc *wakeup;
+ struct regulator *vdd;
+ struct clk *clk;
+ u32 clock_base;
+ u32 i2c_clock_frequency;
+ int state;
+ struct platform_device *gpiodev;
+};
+
+static irqreturn_t sc18is600_irq_handler(int this_irq, void *data)
+{
+ struct sc18is600dev *dev = data;
+ int err;
+
+ err = regmap_read(dev->regmap, SC18IS600_REG_I2C_STAT, &dev->state);
+ if (err)
+ return IRQ_NONE;
+
+ dev_dbg(&dev->spi->dev, "irq received, stat=%08x", dev->state);
+
+ /* no irq is generated for busy state, so ignore this irq */
+ if (dev->state == SC18IS600_STAT_BUSY)
+ return IRQ_NONE;
+
+ complete(&dev->completion);
+ return IRQ_HANDLED;
+}
+
+static int reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct device *dev = context;
+ struct spi_device *spi = to_spi_device(dev);
+ u8 txbuffer[2] = { SC18IS600_CMD_REG_RD, reg & 0xff };
+ u8 rxbuffer[1];
+ int err;
+
+ err = spi_write_then_read(spi, txbuffer, sizeof(txbuffer),
+ rxbuffer, sizeof(rxbuffer));
+ if (err)
+ return err;
+
+ *val = rxbuffer[0];
+
+ return 0;
+}
+
+static int reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct device *dev = context;
+ struct spi_device *spi = to_spi_device(dev);
+ u8 txbuffer[3] = { SC18IS600_CMD_REG_WR, reg & 0xff, val & 0xff };
+
+ return spi_write(spi, txbuffer, sizeof(txbuffer));
+}
+
+static struct regmap_bus regmap_sc18is600_bus = {
+ .reg_write = reg_write,
+ .reg_read = reg_read,
+ .reg_format_endian_default = REGMAP_ENDIAN_BIG,
+ .val_format_endian_default = REGMAP_ENDIAN_BIG,
+};
+
+static void sc18is600_wakeup(struct sc18is600dev *dev)
+{
+ if (!dev->wakeup)
+ return;
+
+ gpiod_set_value_cansleep(dev->wakeup, 1);
+ usleep_range(50, 100);
+ gpiod_set_value_cansleep(dev->wakeup, 0);
+}
+
+static void sc18is600_setup_clock_frequency(struct sc18is600dev *dev)
+{
+ int reg = DIV_ROUND_UP(dev->clock_base, dev->i2c_clock_frequency);
+
+ if (reg < 5)
+ reg = 5;
+ if (reg > 255)
+ reg = 255;
+
+ dev_dbg(&dev->spi->dev, "i2c clock frequency: %08x", reg);
+ regmap_write(dev->regmap, SC18IS600_REG_I2C_CLOCK, reg);
+}
+
+static void sc18is600_setup_timeout(struct sc18is600dev *dev,
+ bool enable, int timeout_ms)
+{
+ int timeout = DIV_ROUND_UP(timeout_ms * dev->chip->timeout_base, 10000);
+ u8 reg;
+
+ if (timeout <= 0)
+ timeout = 1;
+ if (timeout > 255)
+ timeout = 255;
+
+ reg = (timeout & 0x7F) << 1;
+ reg |= (!!enable);
+
+ dev_dbg(&dev->spi->dev, "i2c timeout: %08x", reg);
+ regmap_write(dev->regmap, SC18IS600_REG_I2C_TIMEOUT, reg);
+}
+
+static void sc18is600_reset(struct sc18is600dev *dev)
+{
+ if (dev->reset) {
+ gpiod_set_value_cansleep(dev->reset, 1);
+ usleep_range(50, 100);
+ gpiod_set_value_cansleep(dev->reset, 0);
+ usleep_range(50, 100);
+ }
+
+ sc18is600_setup_clock_frequency(dev);
+ sc18is600_setup_timeout(dev, true, 500);
+}
+
+static int sc18is600_read(struct sc18is600dev *dev, struct i2c_msg *msg)
+{
+ u8 header[] = { SC18IS600_CMD_RD, msg->len, msg->addr << 1 };
+ struct spi_transfer xfer[1] = { 0 };
+
+ xfer[0].tx_buf = header;
+ xfer[0].len = sizeof(header);
+
+ dev_dbg(&dev->spi->dev, "r(addr=%x, len=%d)", msg->addr, msg->len);
+ return spi_sync_transfer(dev->spi, xfer, 1);
+}
+
+static int sc18is600_write(struct sc18is600dev *dev, struct i2c_msg *msg)
+{
+ u8 header[] = { SC18IS600_CMD_WR, msg->len, msg->addr << 1 };
+ struct spi_transfer xfer[2] = { 0 };
+
+ xfer[0].tx_buf = header;
+ xfer[0].len = sizeof(header);
+
+ xfer[1].tx_buf = msg->buf;
+ xfer[1].len = msg->len;
+
+ dev_dbg(&dev->spi->dev, "w(addr=%x, len=%d)", msg->addr, msg->len);
+ return spi_sync_transfer(dev->spi, xfer, 2);
+}
+
+static int sc18is600_read_after_write(struct sc18is600dev *dev,
+ struct i2c_msg *msg1,
+ struct i2c_msg *msg2)
+{
+ u8 header1[] =
+ { SC18IS600_CMD_WR_RD, msg1->len, msg2->len, msg1->addr << 1 };
+ u8 header2[] = { msg2->addr << 1 };
+ struct spi_transfer xfer[3] = { 0 };
+
+ xfer[0].tx_buf = header1;
+ xfer[0].len = sizeof(header1);
+
+ xfer[1].tx_buf = msg1->buf;
+ xfer[1].len = msg1->len;
+
+ xfer[2].tx_buf = header2;
+ xfer[2].len = sizeof(header2);
+
+ dev_dbg(&dev->spi->dev, "w(addr=%x, len=%d) + r(addr=%x, len=%d)",
+ msg1->addr, msg1->len, msg2->addr, msg2->len);
+ return spi_sync_transfer(dev->spi, xfer, 3);
+}
+
+static int sc18is600_write_after_write(struct sc18is600dev *dev,
+ struct i2c_msg *msg1,
+ struct i2c_msg *msg2)
+{
+ u8 header1[] =
+ { SC18IS600_CMD_WR_WR, msg1->len, msg2->len, msg1->addr << 1 };
+ u8 header2[] = { msg2->addr << 1 };
+ struct spi_transfer xfer[4] = { 0 };
+
+ xfer[0].tx_buf = header1;
+ xfer[0].len = sizeof(header1);
+
+ xfer[1].tx_buf = msg1->buf;
+ xfer[1].len = msg1->len;
+
+ xfer[2].tx_buf = header2;
+ xfer[2].len = sizeof(header2);
+
+ xfer[3].tx_buf = msg2->buf;
+ xfer[3].len = msg2->len;
+
+ dev_dbg(&dev->spi->dev, "w(addr=%x, len=%d) + w(addr=%x, len=%d)",
+ msg1->addr, msg1->len, msg2->addr, msg2->len);
+ return spi_sync_transfer(dev->spi, xfer, 4);
+}
+
+static int sc18is600_read_buffer(struct sc18is600dev *dev, struct i2c_msg *msg)
+{
+ static const u8 read_buffer_cmd = SC18IS600_REG_I2C_BUFFER;
+
+ return spi_write_then_read(dev->spi, &read_buffer_cmd, 1,
+ msg->buf, msg->len);
+}
+
+static int __sc18is600_xfer(struct i2c_adapter *adapter,
+ struct i2c_msg *msgs, int num)
+{
+ struct sc18is600dev *dev = adapter->algo_data;
+ int read_operations = 0;
+ int i, err;
+
+ for (i = 0; i < num; i++) {
+ if (msgs[i].len > dev->chip->buffer_size)
+ return -EOPNOTSUPP;
+
+ /* chip only support standard read & write */
+ if (msgs[i].flags & ~I2C_M_RD)
+ return -EOPNOTSUPP;
+
+ if (msgs[i].flags & I2C_M_RD)
+ read_operations++;
+ }
+
+ reinit_completion(&dev->completion);
+
+ if (num == 1 && read_operations == 1)
+ err = sc18is600_read(dev, &msgs[0]);
+ else if (num == 1)
+ err = sc18is600_write(dev, &msgs[0]);
+ else if (num == 2 && read_operations == 1)
+ err = sc18is600_read_after_write(dev, &msgs[0], &msgs[1]);
+ else if (num == 2)
+ err = sc18is600_write_after_write(dev, &msgs[0], &msgs[1]);
+ else
+ return -EOPNOTSUPP;
+
+ if (err) {
+ dev_err(&dev->spi->dev, "spi transfer failed: %d", err);
+ return err;
+ }
+
+ err = wait_for_completion_timeout(&dev->completion, adapter->timeout);
+ if (!err) {
+ dev_warn(&dev->spi->dev,
+ "timeout waiting for irq, poll status register");
+ dev->state = SC18IS600_STAT_BUSY;
+ regmap_read(dev->regmap, SC18IS600_REG_I2C_STAT, &dev->state);
+ }
+
+ switch (dev->state) {
+ case SC18IS600_STAT_OK:
+ break;
+ case SC18IS600_STAT_NAK_ADDR:
+ return -EIO;
+ case SC18IS600_STAT_NAK_DATA:
+ return -EREMOTEIO;
+ case SC18IS600_STAT_SIZE:
+ return -EINVAL;
+ case SC18IS600_STAT_TIMEOUT:
+ return -ETIMEDOUT;
+ case SC18IS600_STAT_TIMEOUT2:
+ return -ETIMEDOUT;
+ case SC18IS600_STAT_BLOCKED:
+ return -ETIMEDOUT;
+ default:
+ case SC18IS600_STAT_BUSY:
+ dev_err(&dev->spi->dev, "device hangup detected, reset!");
+ sc18is600_reset(dev);
+ return -EAGAIN;
+ }
+
+ if (!read_operations)
+ return 0;
+
+ err = sc18is600_read_buffer(dev, &msgs[num-1]);
+ if (err)
+ return err;
+
+ return num;
+}
+
+static int sc18is600_xfer(struct i2c_adapter *adapter,
+ struct i2c_msg *msgs, int num)
+{
+ struct sc18is600dev *dev = adapter->algo_data;
+ int err;
+
+ if (dev->wakeup) {
+ err = pm_runtime_get_sync(&dev->spi->dev);
+ if (err) {
+ pm_runtime_put(&dev->spi->dev);
+ dev_err(&dev->spi->dev, "failed to get RPM: %d", err);
+ return -EIO;
+ }
+ }
+
+ err = __sc18is600_xfer(adapter, msgs, num);
+
+ if (dev->wakeup) {
+ pm_runtime_mark_last_busy(&dev->spi->dev);
+ pm_runtime_put_autosuspend(&dev->spi->dev);
+ }
+
+ return err;
+}
+
+static u32 sc18is600_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm sc18is600_algorithm = {
+ .master_xfer = sc18is600_xfer,
+ .functionality = sc18is600_func,
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id sc18is600_of_match[] = {
+ { .compatible = "nxp,sc18is600", .data = &chip_sc18is600 },
+ { .compatible = "nxp,sc18is601", .data = &chip_sc18is601 },
+ { .compatible = "silabs,cp2120", .data = &chip_cp2120 },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sc18is600_of_match);
+#endif
+
+static int sc18is600_probe(struct spi_device *spi)
+{
+ const struct of_device_id *of_id;
+ struct sc18is600dev *dev;
+ int err;
+
+ of_id = of_match_device(sc18is600_of_match, &spi->dev);
+ if (!of_id)
+ return -ENODEV;
+
+ dev = devm_kzalloc(&spi->dev, sizeof(*dev), GFP_KERNEL);
+ if (dev == NULL)
+ return -ENOMEM;
+ spi_set_drvdata(spi, dev);
+
+ init_completion(&dev->completion);
+
+ dev->spi = spi;
+ dev->adapter.owner = THIS_MODULE;
+ dev->adapter.class = I2C_CLASS_DEPRECATED;
+ dev->adapter.algo = &sc18is600_algorithm;
+ dev->adapter.algo_data = dev;
+ dev->adapter.dev.parent = &spi->dev;
+ dev->chip = of_id->data;
+
+ snprintf(dev->adapter.name, sizeof(dev->adapter.name),
+ "SC18IS600 at SPI %02d device %02d",
+ spi->master->bus_num, spi->chip_select);
+
+ spi->bits_per_word = 8;
+ spi->mode = SPI_MODE_3;
+ spi->max_speed_hz = dev->chip->max_spi_speed;
+
+ err = spi_setup(spi);
+ if (err)
+ return err;
+
+ dev->reset = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(dev->reset)) {
+ err = PTR_ERR(dev->reset);
+ dev_err(&spi->dev, "Failed to reset gpio, err: %d\n", err);
+ return err;
+ }
+
+ dev->wakeup = devm_gpiod_get_optional(&spi->dev, "wakeup",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(dev->wakeup)) {
+ err = PTR_ERR(dev->wakeup);
+ dev_err(&spi->dev, "Failed to wakeup gpio, err: %d\n", err);
+ return err;
+ }
+
+ err = devm_request_threaded_irq(&spi->dev, spi->irq, NULL,
+ sc18is600_irq_handler,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ "sc18is600", dev);
+ if (err) {
+ dev_err(&spi->dev, "Failed to request irq, err: %d\n", err);
+ return err;
+ }
+
+ dev->regmap = devm_regmap_init(&dev->spi->dev,
+ &regmap_sc18is600_bus, &dev->spi->dev,
+ dev->chip->regmap_cfg);
+ if (IS_ERR(dev->regmap)) {
+ err = PTR_ERR(dev->regmap);
+ dev_err(&spi->dev, "Failed to init regmap, err: %d\n", err);
+ return err;
+ }
+
+ err = device_property_read_u32(&spi->dev, "clock-frequency",
+ &dev->i2c_clock_frequency);
+ if (err) {
+ dev->i2c_clock_frequency = SC18IS600_DEFAULT_FREQ;
+ dev_dbg(&spi->dev, "using default frequency %u\n",
+ dev->i2c_clock_frequency);
+ }
+
+ dev->vdd = devm_regulator_get(&spi->dev, "vdd");
+ if (IS_ERR(dev->vdd)) {
+ err = PTR_ERR(dev->vdd);
+ dev_err(&spi->dev, "could not acquire vdd: %d\n", err);
+ return err;
+ }
+
+ if (!dev->chip->clock_base) {
+ dev->clk = devm_clk_get(&spi->dev, "clkin");
+ if (IS_ERR(dev->clk)) {
+ err = PTR_ERR(dev->clk);
+ dev_err(&spi->dev, "could not acquire vdd: %d\n", err);
+ return err;
+ }
+
+ clk_prepare_enable(dev->clk);
+
+ dev->clock_base = clk_get_rate(dev->clk) / 4;
+ } else {
+ dev->clock_base = dev->chip->clock_base;
+ }
+
+ err = regulator_enable(dev->vdd);
+ if (err) {
+ dev_err(&spi->dev, "could not enable vdd: %d\n", err);
+ return err;
+ }
+
+ sc18is600_reset(dev);
+
+ err = i2c_add_adapter(&dev->adapter);
+ if (err)
+ goto err1;
+
+ dev->gpiodev = platform_device_register_data(&spi->dev,
+ "gpio-sc18is600", -1,
+ &dev->chip->type, sizeof(dev->chip->type));
+ if (IS_ERR(dev->gpiodev)) {
+ err = PTR_ERR(dev->gpiodev);
+ dev_err(&spi->dev, "could not register gpio dev: %d\n", err);
+ goto err2;
+ }
+
+ if (dev->wakeup) {
+ pm_runtime_enable(&spi->dev);
+ pm_runtime_set_autosuspend_delay(&spi->dev,
+ SC18IS600_I2C_PM_TIMEOUT);
+ pm_runtime_use_autosuspend(&spi->dev);
+ clk_disable_unprepare(dev->clk);
+ }
+
+ return 0;
+
+err2:
+ i2c_del_adapter(&dev->adapter);
+err1:
+ regulator_disable(dev->vdd);
+ return err;
+}
+
+static int sc18is600_remove(struct spi_device *spi)
+{
+ struct sc18is600dev *dev = spi_get_drvdata(spi);
+
+ platform_device_unregister(dev->gpiodev);
+
+ i2c_del_adapter(&dev->adapter);
+
+ if (dev->wakeup) {
+ pm_runtime_dont_use_autosuspend(&spi->dev);
+ pm_runtime_disable(&spi->dev);
+ } else {
+ regulator_disable(dev->vdd);
+ }
+
+ return 0;
+}
+
+static int sc18is600_runtime_suspend(struct device *dev)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct sc18is600dev *spidev = spi_get_drvdata(spi);
+ u8 buffer[3] = {SC18IS600_CMD_PWRDWN, 0x5a, 0xa5};
+ int err;
+
+ if (!spidev->wakeup)
+ return -EOPNOTSUPP;
+
+ dev_dbg(dev, "SUSPEND\n");
+
+ err = spi_write(spidev->spi, buffer, 3);
+ if (err)
+ return err;
+
+ clk_disable_unprepare(spidev->clk);
+
+ return 0;
+}
+
+static int sc18is600_runtime_resume(struct device *dev)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct sc18is600dev *spidev = spi_get_drvdata(spi);
+
+ clk_prepare_enable(spidev->clk);
+
+ if (!spidev->wakeup)
+ return -EOPNOTSUPP;
+
+ dev_dbg(dev, "RESUME\n");
+
+ sc18is600_wakeup(spidev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops sc18is600_pm_ops = {
+ SET_RUNTIME_PM_OPS(sc18is600_runtime_suspend,
+ sc18is600_runtime_resume,
+ NULL)
+};
+
+static struct spi_driver sc18is600_driver = {
+ .probe = sc18is600_probe,
+ .remove = sc18is600_remove,
+ .driver = {
+ .name = "i2c-sc18is600",
+ .pm = &sc18is600_pm_ops,
+ .of_match_table = of_match_ptr(sc18is600_of_match),
+ },
+};
+module_spi_driver(sc18is600_driver);
+
+MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
+MODULE_DESCRIPTION("NXP SC18IS600 I2C bus adapter");
+MODULE_LICENSE("GPL");