summaryrefslogtreecommitdiffstats
path: root/drivers/gpio/gpio-sc18is600.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpio/gpio-sc18is600.c')
-rw-r--r--drivers/gpio/gpio-sc18is600.c134
1 files changed, 134 insertions, 0 deletions
diff --git a/drivers/gpio/gpio-sc18is600.c b/drivers/gpio/gpio-sc18is600.c
new file mode 100644
index 000000000000..cfc3398834a1
--- /dev/null
+++ b/drivers/gpio/gpio-sc18is600.c
@@ -0,0 +1,134 @@
+#include <linux/gpio.h>
+#include <linux/regmap.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+
+#define SC18IS600_REG_IO_CONFIG 0x00
+#define SC18IS600_REG_IO_STATE 0x01
+#define SC18IS600_REG_IO_CONFIG2 0x07 /* only cp2120 */
+
+struct gpio_sc18is600 {
+ struct gpio_chip chip;
+ struct regmap *regmap;
+};
+
+static int sc18is600_mode(struct gpio_sc18is600 *sc18is600, u8 gpio, u8 mode)
+{
+ u8 reg = (gpio <= 4) ?
+ SC18IS600_REG_IO_CONFIG : SC18IS600_REG_IO_CONFIG2;
+
+ u8 mask = (0x03 << (gpio % 4));
+ u8 val = (mode << (gpio % 4));
+
+ return regmap_update_bits(sc18is600->regmap, reg, mask, val);
+}
+
+void sc18is600_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct gpio_sc18is600 *sc18is600 = gpiochip_get_data(chip);
+ u8 reg = SC18IS600_REG_IO_STATE;
+ u8 mask = (1 << offset);
+ u8 val = value ? 0xff : 0x00;
+ int err;
+
+ err = regmap_update_bits(sc18is600->regmap, reg, mask, val);
+ if (err)
+ dev_err(chip->parent, "regmap failed: %d", err);
+}
+
+static int sc18is600_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct gpio_sc18is600 *sc18is600 = gpiochip_get_data(chip);
+ u8 reg = SC18IS600_REG_IO_STATE;
+ u8 mask = (1 << offset);
+ int val, err;
+
+ err = regmap_read(sc18is600->regmap, reg, &val);
+ if (err) {
+ dev_err(chip->parent, "regmap failed: %d", err);
+ return err;
+ }
+
+ return val & mask;
+}
+
+static int sc18is600_input(struct gpio_chip *chip, unsigned offset)
+{
+ struct gpio_sc18is600 *sc18is600 = gpiochip_get_data(chip);
+
+ /* input only mode */
+ return sc18is600_mode(sc18is600, offset, 0x01);
+}
+
+static int sc18is600_output(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct gpio_sc18is600 *sc18is600 = gpiochip_get_data(chip);
+ int err;
+
+ /* push-pull mode */
+ err = sc18is600_mode(sc18is600, offset, 0x02);
+ if (err)
+ return err;
+
+ sc18is600_set(chip, offset, value);
+
+ return 0;
+}
+
+static int gpio_sc18is600_probe(struct platform_device *pdev)
+{
+ struct gpio_sc18is600 *sc18is600;
+ u8 *chiptype = dev_get_platdata(&pdev->dev);
+ int err;
+
+ sc18is600 = devm_kzalloc(&pdev->dev, sizeof(*sc18is600), GFP_KERNEL);
+ if (!sc18is600)
+ return -ENOMEM;
+
+ sc18is600->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!sc18is600->regmap) {
+ dev_err(&pdev->dev, "missing regmap!");
+ return -ENODEV;
+ }
+
+ if (!chiptype || *chiptype > 0x02) {
+ dev_err(&pdev->dev, "missing chiptype!");
+ return -ENXIO;
+ }
+
+ sc18is600->chip.base = -1;
+ sc18is600->chip.direction_input = sc18is600_input;
+ sc18is600->chip.get = sc18is600_get;
+ sc18is600->chip.direction_output = sc18is600_output;
+ sc18is600->chip.set = sc18is600_set;
+ sc18is600->chip.can_sleep = true;
+ sc18is600->chip.parent = &pdev->dev;
+ sc18is600->chip.owner = THIS_MODULE;
+
+ if (*chiptype == 0x02) {
+ sc18is600->chip.ngpio = 8;
+ sc18is600->chip.label = "cp2120";
+ } else {
+ sc18is600->chip.ngpio = 4;
+ sc18is600->chip.label = "sc18is600";
+ }
+
+ err = devm_gpiochip_add_data(&pdev->dev, &sc18is600->chip, sc18is600);
+ if (err)
+ dev_err(&pdev->dev, "failed to add gpiochip: %d", err);
+
+ return err;
+}
+
+static struct platform_driver sc18is600_driver = {
+ .driver = {
+ .name = "gpio-sc18is600",
+ },
+ .probe = gpio_sc18is600_probe,
+};
+module_platform_driver(sc18is600_driver);
+
+MODULE_ALIAS("platform:gpio-sc18is600");
+MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
+MODULE_DESCRIPTION("NXP SC18IS600 GPIO interface");
+MODULE_LICENSE("GPL");