build/patch/kernel/archive/sunxi-5.15/patches.megous/regulator-tp65185x-Add-tp65185x-eInk-panel-regulator-driver.patch

490 lines
12 KiB
Diff

From d7de0d053e7cda8bd1571c187ad94dbe78f68fb3 Mon Sep 17 00:00:00 2001
From: Ondrej Jirman <megous@megous.com>
Date: Sat, 5 Oct 2019 15:12:51 +0200
Subject: [PATCH 194/478] regulator: tp65185x: Add tp65185x eInk panel
regulator driver
This is a very simple driver that doesn't try to expose individual
regulators, and implements the specific power sequencing necessary
for the eInk panel power up directly.
All the 6 regulators are thus represented as one logical supply.
VCOM output is the only voltage that needs setting, and it can
be changed by setting the voltage of the regulator exported by
the driver.
Signed-off-by: Ondrej Jirman <megous@megous.com>
---
drivers/regulator/Kconfig | 8 +
drivers/regulator/Makefile | 1 +
drivers/regulator/tp65185x.c | 428 +++++++++++++++++++++++++++++++++++
3 files changed, 437 insertions(+)
create mode 100644 drivers/regulator/tp65185x.c
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 271f73561125..5622742303dc 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -1432,5 +1432,13 @@ config REGULATOR_QCOM_LABIBB
boost regulator and IBB can be used as a negative boost regulator
for LCD display panel.
+config REGULATOR_TP65185X
+ tristate "eInk display regulator tp65185x"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ This driver provides support for the voltage regulators for
+ eInk displays on various e-book readers.
+
endif
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 3a7af4b039c5..5b1cb1e76856 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -172,5 +172,6 @@ obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o
obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o
obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o
obj-$(CONFIG_REGULATOR_WM8994) += wm8994-regulator.o
+obj-$(CONFIG_REGULATOR_TP65185X) += tp65185x.o
ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG
diff --git a/drivers/regulator/tp65185x.c b/drivers/regulator/tp65185x.c
new file mode 100644
index 000000000000..777b91cd34cd
--- /dev/null
+++ b/drivers/regulator/tp65185x.c
@@ -0,0 +1,428 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Regulator device driver for tp65185x (eInk panel regulator)
+//
+// Copyright (C) 2019 Ondřej Jirman <megous@megous.com>
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/gpio/consumer.h>
+#include <linux/delay.h>
+
+#define REG_TMST_VALUE 0x00
+#define REG_ENABLE 0x01
+#define REG_VADJ 0x02
+#define REG_VCOM1 0x03
+#define REG_VCOM2 0x04
+#define REG_INT_EN1 0x05
+#define REG_INT_EN2 0x06
+#define REG_INT1 0x07
+#define REG_INT2 0x08
+#define REG_UPSEQ0 0x09
+#define REG_UPSEQ1 0x0A
+#define REG_DWNSEQ0 0x0B
+#define REG_DWNSEQ1 0x0C
+#define REG_TMST1 0x0D
+#define REG_TMST2 0x0E
+#define REG_PG 0x0F
+#define REG_REVID 0x10
+
+struct tp65185x {
+ struct device* dev;
+ struct gpio_desc* wakeup_gpio;
+ struct gpio_desc* vcom_gpio;
+ struct gpio_desc* powerup_gpio;
+ struct gpio_desc* powergood_gpio;
+ struct regmap *regmap;
+ bool is_enabled;
+ int vcom;
+};
+
+static const struct regmap_config tp65185x_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x10,
+ .cache_type = REGCACHE_NONE,
+};
+
+struct voltage_rail {
+ const char* name;
+ int bit;
+ int bit_en;
+};
+
+static const struct voltage_rail rails[] = {
+ { "VB", 7, -1 },
+ { "VDDH", 6, 3 },
+ { "VN", 5, -1 },
+ { "VPOS", 4, 2 },
+ { "VEE", 3, 1 },
+ { "VNEG", 1, 0 },
+ { "VCOM", -1, 4 },
+ { "V3P3", -1, 5 },
+};
+
+static int apply_voltage(struct tp65185x *tp, int vcom)
+{
+ int ret;
+
+ ret = regmap_write(tp->regmap, REG_VCOM1, vcom & 0xff);
+ if (ret)
+ return ret;
+
+ return regmap_write(tp->regmap, REG_VCOM2, (vcom >> 8) & 1);
+}
+
+static int set_voltage(struct regulator_dev *rdev, int min_uV, int max_uV,
+ unsigned *selector)
+{
+ struct tp65185x* tp = rdev_get_drvdata(rdev);
+ unsigned int vcom = min_uV / 10000;
+
+ if (vcom > 511 || min_uV < 0)
+ return -EINVAL;
+
+ tp->vcom = vcom;
+
+ // setup VCOM
+
+ if (!tp->is_enabled)
+ return 0;
+
+ return apply_voltage(tp, vcom);
+}
+
+static int get_voltage(struct regulator_dev *rdev)
+{
+ struct tp65185x* tp = rdev_get_drvdata(rdev);
+ unsigned int lsb, msb;
+ int ret;
+
+ if (tp->is_enabled) {
+ ret = regmap_read(rdev->regmap, REG_VCOM1, &lsb);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(rdev->regmap, REG_VCOM2, &msb);
+ if (ret)
+ return ret;
+
+ return (lsb | ((msb & 1) << 8)) * 10000;
+ }
+
+ return tp->vcom * 10000;
+}
+
+#ifdef DEBUG
+static int show_power_status(struct tp65185x* tp, const char* state)
+{
+ unsigned int reg;
+ int i, ret;
+
+ ret = regmap_read(tp->regmap, REG_PG, &reg);
+ if (ret)
+ return ret;
+
+ dev_warn(tp->dev, "%s:\n", state);
+
+ dev_warn(tp->dev, " voltage rail power good:\n");
+ for (i = 0; i < ARRAY_SIZE(rails); i++)
+ if (rails[i].bit >= 0)
+ dev_warn(tp->dev, " %s %s\n", rails[i].name,
+ reg & BIT(rails[i].bit) ? "good" : "fail");
+
+ ret = regmap_read(tp->regmap, REG_ENABLE, &reg);
+ if (ret)
+ return ret;
+
+ dev_warn(tp->dev, " voltage rail enable status:\n");
+ for (i = 0; i < ARRAY_SIZE(rails); i++)
+ if (rails[i].bit_en >= 0)
+ dev_warn(tp->dev, " %s %s\n", rails[i].name,
+ reg & BIT(rails[i].bit_en)
+ ? "enabled" : "disabled");
+
+ return 0;
+}
+#else
+static int show_power_status(struct tp65185x* tp, const char* state)
+{
+ return 0;
+}
+#endif
+
+static int show_power_bad(struct tp65185x* tp)
+{
+ unsigned int reg;
+ int i, ret;
+
+ ret = regmap_read(tp->regmap, REG_PG, &reg);
+ if (ret)
+ return ret;
+
+ dev_warn(tp->dev, "Voltage rail failures:\n");
+ for (i = 0; i < ARRAY_SIZE(rails); i++)
+ if (rails[i].bit >= 0 && !(reg & BIT(rails[i].bit)))
+ dev_warn(tp->dev, " %s failed\n", rails[i].name);
+
+ return 0;
+}
+
+static int wait_for_power_good(struct tp65185x *tp)
+{
+ int ret, loops = 10;
+
+ // wait for power good
+ while (loops-- > 0) {
+ ret = gpiod_get_value(tp->powergood_gpio);
+ if (ret < 0)
+ return ret;
+
+ if (ret)
+ return 0;
+
+ msleep(5);
+ }
+
+ show_power_bad(tp);
+ return -ETIMEDOUT;
+}
+
+static int enable_supply(struct regulator_dev *rdev)
+{
+ struct tp65185x* tp = rdev_get_drvdata(rdev);
+ int ret;
+
+ if (tp->is_enabled)
+ return 0;
+
+ // wake up the regulator
+ gpiod_set_value(tp->wakeup_gpio, 1);
+ usleep_range(3000, 4000);
+
+ ret = apply_voltage(tp, tp->vcom);
+ if (ret) {
+ dev_err(tp->dev, "vcom restore failed (%d)\n", ret);
+ goto err;
+ }
+
+ show_power_status(tp, "pre-powerup");
+
+ // enable the VDD on the panel (V3P3) first
+ ret = regmap_write(tp->regmap, REG_ENABLE, 0x20);
+ if (ret) {
+ dev_err(tp->dev, "vdd enable failed (%d)\n", ret);
+ goto err;
+ }
+
+ usleep_range(2000, 2200);
+
+ show_power_status(tp, "V3P3 en");
+
+ // powerup by default takes about 20ms
+ gpiod_set_value(tp->powerup_gpio, 1);
+ usleep_range(22000, 24000);
+
+ ret = wait_for_power_good(tp);
+ if (ret)
+ goto err;
+
+ show_power_status(tp, "powerup done");
+
+ // enable VCOM last
+ gpiod_set_value(tp->vcom_gpio, 1);
+ usleep_range(4000, 5000);
+
+ show_power_status(tp, "powerup vcom");
+
+ tp->is_enabled = true;
+ return 0;
+
+err:
+ gpiod_set_value(tp->vcom_gpio, 0);
+ usleep_range(2000, 3000);
+ gpiod_set_value(tp->powerup_gpio, 0);
+ msleep(100);
+ gpiod_set_value(tp->wakeup_gpio, 0);
+ return ret;
+}
+
+static int disable_supply(struct regulator_dev *rdev)
+{
+ struct tp65185x* tp = rdev_get_drvdata(rdev);
+
+ if (!tp->is_enabled)
+ return 0;
+
+ show_power_status(tp, "pre-poweroff");
+
+ gpiod_set_value(tp->vcom_gpio, 0);
+ usleep_range(5000, 6000);
+
+ show_power_status(tp, "vcom down");
+
+ gpiod_set_value(tp->powerup_gpio, 0);
+
+ // it may take up to 100ms to power off all high voltage rails
+ msleep(100);
+
+ show_power_status(tp, "power down");
+
+ // this will power down the V3P3 switch too
+ gpiod_set_value(tp->wakeup_gpio, 0);
+
+ tp->is_enabled = false;
+
+ return 0;
+}
+
+static int is_supply_enabled(struct regulator_dev *rdev)
+{
+ struct tp65185x* tp = rdev_get_drvdata(rdev);
+ //int ret;
+
+ //ret = gpiod_get_value(tp->powergood_gpio);
+ //if (ret < 0)
+ //return ret;
+
+ return tp->is_enabled;
+}
+
+static const struct regulator_ops tp65185x_ops = {
+ .is_enabled = is_supply_enabled,
+ .enable = enable_supply,
+ .disable = disable_supply,
+ .set_voltage = set_voltage,
+ .get_voltage = get_voltage,
+};
+
+static const struct regulator_desc tp65185x_reg = {
+ .name = "tp65185x",
+ .id = 0,
+ .continuous_voltage_range = 1,
+ .ops = &tp65185x_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+};
+
+static int tp65185x_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &i2c->dev;
+ struct regulator_dev *rdev;
+ struct regulator_config config = { };
+ unsigned int reg;
+ struct tp65185x* tp;
+ const char* rev = NULL;
+ int ret;
+
+ tp = devm_kzalloc(dev, sizeof(*tp), GFP_KERNEL);
+ if (!tp)
+ return -ENOMEM;
+
+ tp->dev = dev;
+
+ tp->powergood_gpio = devm_gpiod_get(dev, "powergood", GPIOD_IN);
+ if (IS_ERR(tp->powergood_gpio)) {
+ ret = PTR_ERR(tp->powergood_gpio);
+ dev_err(dev, "Can't get wakeup gpio (%d)\n", ret);
+ return ret;
+ }
+
+ tp->powerup_gpio = devm_gpiod_get(dev, "powerup", GPIOD_OUT_LOW);
+ if (IS_ERR(tp->powerup_gpio)) {
+ ret = PTR_ERR(tp->powerup_gpio);
+ dev_err(dev, "Can't get wakeup gpio (%d)\n", ret);
+ return ret;
+ }
+
+ tp->vcom_gpio = devm_gpiod_get(dev, "vcom", GPIOD_OUT_LOW);
+ if (IS_ERR(tp->vcom_gpio)) {
+ ret = PTR_ERR(tp->vcom_gpio);
+ dev_err(dev, "Can't get wakeup gpio (%d)\n", ret);
+ return ret;
+ }
+
+ tp->wakeup_gpio = devm_gpiod_get(dev, "wakeup", GPIOD_OUT_HIGH);
+ if (IS_ERR(tp->wakeup_gpio)) {
+ ret = PTR_ERR(tp->wakeup_gpio);
+ dev_err(dev, "Can't get wakeup gpio (%d)\n", ret);
+ return ret;
+ }
+
+ // wait for wakeup
+ usleep_range(10000, 12000);
+
+ i2c_set_clientdata(i2c, tp);
+
+ tp->regmap = devm_regmap_init_i2c(i2c, &tp65185x_regmap_config);
+ if (IS_ERR(tp->regmap)) {
+ ret = PTR_ERR(tp->regmap);
+ dev_err(dev, "Failed to allocate register map: %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_read(tp->regmap, REG_REVID, &reg);
+ if (ret) {
+ dev_err(dev, "chip id read failed (%d)\n", ret);
+ return ret;
+ }
+
+ switch (reg) {
+ case 0x45: rev = "TPS65185 1p0"; break;
+ case 0x55: rev = "TPS65185 1p1"; break;
+ case 0x65: rev = "TPS65185 1p2"; break;
+ case 0x66: rev = "TPS651851 1p0"; break;
+ default:
+ dev_err(dev, "reading chip id\n");
+ break;
+
+ }
+
+ dev_info(dev, "detected chip id 0x%02x (%s)\n", (int)reg, rev);
+
+ // disable regulators, move to sleep
+ gpiod_set_value(tp->wakeup_gpio, 0);
+
+ config.driver_data = tp;
+ config.dev = &i2c->dev;
+ config.regmap = tp->regmap;
+ config.of_node = dev->of_node;
+ config.init_data = of_get_regulator_init_data(dev, dev->of_node,
+ &tp65185x_reg);
+ if (!config.init_data)
+ return -ENOMEM;
+
+ rdev = devm_regulator_register(&i2c->dev, &tp65185x_reg, &config);
+ if (IS_ERR(rdev)) {
+ ret = PTR_ERR(rdev);
+ dev_err(&i2c->dev, "Failed to register egulator (%d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct i2c_device_id tp65185x_i2c_id[] = {
+ { "tp65185x", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, tp65185x_i2c_id);
+
+static struct i2c_driver tp65185x_regulator_driver = {
+ .driver = {
+ .name = "tp65185x",
+ },
+ .probe = tp65185x_i2c_probe,
+ .id_table = tp65185x_i2c_id,
+};
+
+module_i2c_driver(tp65185x_regulator_driver);
+
+MODULE_AUTHOR("Ondřej Jirman <megous@megous.com>");
+MODULE_DESCRIPTION("Regulator device driver for tp65185x");
+MODULE_LICENSE("GPL");
--
2.35.3