mfd: rk808: add rk805 support
[firefly-linux-kernel-4.4.55.git] / drivers / gpio / gpio-rk8xx.c
diff --git a/drivers/gpio/gpio-rk8xx.c b/drivers/gpio/gpio-rk8xx.c
new file mode 100644 (file)
index 0000000..e7707e2
--- /dev/null
@@ -0,0 +1,262 @@
+/*
+ * drivers/gpio/gpio-rk8xx.c
+ * Driver for Rockchip RK8xx PMIC GPIO
+ *
+ * Copyright (C) 2017, Rockchip Technology Co., Ltd.
+ * Author: Chen Jianhong <chenjh@rock-chips.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/mfd/rk808.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#define RK805_GPIO_REG         RK805_OUT_REG
+#define RK805_OUT0_VALMASK     BIT(0)
+#define RK805_OUT1_VALMASK     BIT(1)
+
+struct rk8xx_gpio_reg {
+       u8 reg;
+       u8 dir_msk;
+       u8 val_msk;
+       u8 fun_msk;
+};
+
+struct rk8xx_gpio_info {
+       struct rk808 *rk8xx;
+       struct gpio_chip gpio_chip;
+       struct rk8xx_gpio_reg *gpio_reg;
+       int gpio_nr;
+};
+
+static int rk8xx_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+       int err;
+       struct rk8xx_gpio_info *gi = dev_get_drvdata(chip->dev);
+
+       /* iomux */
+       if (gi->gpio_reg[offset].fun_msk) {
+               err = regmap_update_bits(gi->rk8xx->regmap,
+                                        gi->gpio_reg[offset].reg,
+                                        gi->gpio_reg[offset].fun_msk,
+                                        gi->gpio_reg[offset].fun_msk);
+               if (err) {
+                       dev_err(chip->dev, "set gpio%d func fail: %d\n",
+                               offset, err);
+                       return err;
+               }
+       }
+
+       /* direction */
+       if (gi->gpio_reg[offset].dir_msk) {
+               err = regmap_update_bits(gi->rk8xx->regmap,
+                                        gi->gpio_reg[offset].reg,
+                                        gi->gpio_reg[offset].dir_msk,
+                                        0);
+               if (err) {
+                       dev_err(chip->dev, "set gpio%d input fail: %d\n",
+                               offset, err);
+                       return err;
+               }
+       }
+
+       return 0;
+}
+
+static int rk8xx_gpio_direction_output(struct gpio_chip *chip,
+                                      unsigned offset, int value)
+{
+       int err;
+       struct rk8xx_gpio_info *gi = dev_get_drvdata(chip->dev);
+
+       /* iomux */
+       if (gi->gpio_reg[offset].fun_msk) {
+               err = regmap_update_bits(gi->rk8xx->regmap,
+                                        gi->gpio_reg[offset].reg,
+                                        gi->gpio_reg[offset].fun_msk,
+                                        gi->gpio_reg[offset].fun_msk);
+               if (err) {
+                       dev_err(chip->dev, "set gpio%d func fail: %d\n",
+                               offset, err);
+                       return err;
+               }
+       }
+
+       /* direction */
+       if (gi->gpio_reg[offset].dir_msk) {
+               err = regmap_update_bits(gi->rk8xx->regmap,
+                                        gi->gpio_reg[offset].reg,
+                                        gi->gpio_reg[offset].dir_msk,
+                                        gi->gpio_reg[offset].dir_msk);
+               if (err) {
+                       dev_err(chip->dev,
+                               "set gpio%d output fail: %d\n", offset, err);
+                       return err;
+               }
+       }
+
+       if (value)
+               err = regmap_update_bits(gi->rk8xx->regmap,
+                                        gi->gpio_reg[offset].reg,
+                                        gi->gpio_reg[offset].val_msk,
+                                        gi->gpio_reg[offset].val_msk);
+       else
+               err = regmap_update_bits(gi->rk8xx->regmap,
+                                        gi->gpio_reg[offset].reg,
+                                        gi->gpio_reg[offset].val_msk,
+                                        0);
+       if (err) {
+               dev_err(chip->dev, "set gpio%d value fail: %d\n", offset, err);
+               return err;
+       }
+
+       return 0;
+}
+
+static int rk8xx_gpio_get_value(struct gpio_chip *chip, unsigned offset)
+{
+       int err;
+       unsigned int val;
+       struct rk8xx_gpio_info *gi = dev_get_drvdata(chip->dev);
+
+       err = regmap_read(gi->rk8xx->regmap, gi->gpio_reg[offset].reg, &val);
+       if (err) {
+               dev_err(chip->dev, "get gpio%d value fail: %d\n", offset, err);
+               return err;
+       }
+
+       return (val & gi->gpio_reg[offset].val_msk) ? 1 : 0;
+}
+
+static void rk8xx_gpio_set_value(struct gpio_chip *chip,
+                                unsigned offset, int value)
+{
+       int err;
+       struct rk8xx_gpio_info *gi = dev_get_drvdata(chip->dev);
+
+       if (value)
+               err = regmap_update_bits(gi->rk8xx->regmap,
+                                        gi->gpio_reg[offset].reg,
+                                        gi->gpio_reg[offset].val_msk,
+                                        gi->gpio_reg[offset].val_msk);
+       else
+               err = regmap_update_bits(gi->rk8xx->regmap,
+                                        gi->gpio_reg[offset].reg,
+                                        gi->gpio_reg[offset].val_msk,
+                                        0);
+       if (err)
+               dev_err(chip->dev, "set gpio%d value fail: %d\n", offset, err);
+}
+
+/* rk805: two gpio: output only */
+static struct rk8xx_gpio_reg rk805_gpio_reg[] = {
+       {
+               .reg = RK805_GPIO_REG,
+               .val_msk = RK805_OUT0_VALMASK,
+       },
+       {
+               .reg = RK805_GPIO_REG,
+               .val_msk = RK805_OUT1_VALMASK,
+       },
+};
+
+static int rk8xx_gpio_probe(struct platform_device *pdev)
+{
+       struct rk808 *rk8xx = dev_get_drvdata(pdev->dev.parent);
+       struct rk8xx_gpio_info *gi;
+       struct device_node *np;
+       int ret;
+
+       np = of_find_node_by_name(pdev->dev.parent->of_node, "gpio");
+       if (np) {
+               if (!of_device_is_available(np)) {
+                       dev_info(&pdev->dev, "device is disabled\n");
+                       return -EINVAL;
+               }
+       }
+
+       gi = devm_kzalloc(&pdev->dev, sizeof(*gi), GFP_KERNEL);
+       if (!gi)
+               return -ENOMEM;
+
+       switch (rk8xx->variant) {
+       case RK805_ID:
+               gi->gpio_reg = rk805_gpio_reg;
+               gi->gpio_nr = ARRAY_SIZE(rk805_gpio_reg);
+               break;
+       default:
+               dev_err(&pdev->dev, "unsupported RK8XX ID %lu\n",
+                       rk8xx->variant);
+               return -EINVAL;
+       }
+
+       gi->rk8xx = rk8xx;
+       gi->gpio_chip.base = -1;
+       gi->gpio_chip.can_sleep = true;
+       gi->gpio_chip.dev = &pdev->dev;
+       gi->gpio_chip.ngpio = gi->gpio_nr;
+       gi->gpio_chip.label = pdev->name;
+       gi->gpio_chip.get = rk8xx_gpio_get_value;
+       gi->gpio_chip.set = rk8xx_gpio_set_value;
+       gi->gpio_chip.direction_input = rk8xx_gpio_direction_input;
+       gi->gpio_chip.direction_output = rk8xx_gpio_direction_output;
+       gi->gpio_chip.owner = THIS_MODULE;
+#ifdef CONFIG_OF_GPIO
+       gi->gpio_chip.of_node = rk8xx->i2c->dev.of_node;
+#endif
+       platform_set_drvdata(pdev, gi);
+
+       ret = gpiochip_add(&gi->gpio_chip);
+       if (ret)
+               dev_err(&pdev->dev, "register rk8xx gpiochip fail: %d\n", ret);
+
+       return ret;
+}
+
+static int rk8xx_gpio_remove(struct platform_device *pdev)
+{
+       struct rk8xx_gpio_info *gi = platform_get_drvdata(pdev);
+
+       gpiochip_remove(&gi->gpio_chip);
+
+       return 0;
+}
+
+static struct platform_driver rk8xx_gpio_driver = {
+       .probe = rk8xx_gpio_probe,
+       .remove = rk8xx_gpio_remove,
+       .driver = {
+               .name = "rk8xx-gpio",
+               .owner = THIS_MODULE,
+       },
+};
+
+static int rk8xx_gpio_init(void)
+{
+       return platform_driver_register(&rk8xx_gpio_driver);
+}
+subsys_initcall(rk8xx_gpio_init);
+
+static void rk8xx_gpio_exit(void)
+{
+       platform_driver_unregister(&rk8xx_gpio_driver);
+}
+module_exit(rk8xx_gpio_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("RK8xx GPIO driver");
+MODULE_AUTHOR("Chen Jianhong <chenjh@rock-chips.com>");