From 28fafa60ceb2c253a9a7673a6daee0855ce829bf Mon Sep 17 00:00:00 2001
From: Ondrej Jirman <megous@megous.com>
Date: Fri, 21 Jan 2022 23:26:17 +0100
Subject: [PATCH 317/446] input: kb151: Add support for IP5209 charger
 connected to the keyboard MCU

IP5209 charger for the internal battery inside the keyboard is accessible
only via the keyboard MCU. Export access to the charger via standard
power_supply interface.

Signed-off-by: Ondrej Jirman <megous@megous.com>
---
 drivers/input/keyboard/kb151.c | 206 +++++++++++++++++++++++++++++++++
 1 file changed, 206 insertions(+)

diff --git a/drivers/input/keyboard/kb151.c b/drivers/input/keyboard/kb151.c
index 41a68f0c3..898a25d06 100644
--- a/drivers/input/keyboard/kb151.c
+++ b/drivers/input/keyboard/kb151.c
@@ -8,7 +8,9 @@
 #include <linux/input/matrix_keypad.h>
 #include <dt-bindings/input/input.h>
 #include <linux/interrupt.h>
+#include <linux/regmap.h>
 #include <linux/module.h>
+#include <linux/delay.h>
 #include <linux/pm_wakeirq.h>
 
 #define KB151_CRC8_POLYNOMIAL		0x07
@@ -25,6 +27,56 @@
 #define KB151_SYS_CONFIG		0x20
 #define KB151_SYS_CONFIG_DISABLE_SCAN	BIT(0)
 
+/* imported from firmware/registers.h */
+
+#define REG_DEVID_K		0x00
+#define REG_DEVID_B		0x01
+#define REG_FW_REVISION		0x02
+#define REG_FW_FEATURES		0x03
+#define REG_FW_FEATURES_USB_DEBUGGER	BIT(0)
+#define REG_FW_FEATURES_FLASHING_MODE	BIT(1)
+#define REG_FW_FEATURES_SELF_TEST	BIT(2)
+#define REG_FW_FEATURES_STOCK_FW	BIT(3)
+#define REG_FW_FEATURES_I2CA		BIT(4)
+
+#define REG_KEYMATRIX_SIZE	0x06
+#define REG_KEYMATRIX_STATE_CRC8 0x07
+#define REG_KEYMATRIX_STATE	0x08
+#define REG_KEYMATRIX_STATE_END	0x13
+
+#define REG_SYS_CONFIG		0x20
+#define REG_SYS_CONFIG_SCAN_BLOCK	BIT(0)
+
+#define REG_SYS_CHG_ADDR	0x21
+#define REG_SYS_CHG_DATA	0x22
+
+#define REG_SYS_COMMAND		0x23
+#define REG_SYS_COMMAND_MCU_RESET	'r'
+#define REG_SYS_COMMAND_USB_IAP		'i'
+#define REG_SYS_COMMAND_SELFTEST	't'
+#define REG_SYS_COMMAND_CHG_READ	0x91
+#define REG_SYS_COMMAND_CHG_WRITE	0xA1
+
+#define REG_SYS_USER_APP_BLOCK	0x24
+#define REG_SYS_USER_APP_BLOCK_MAGIC	0x53
+
+#define REG_FLASH_DATA_START	0x70
+#define REG_FLASH_DATA_END	0xef
+#define REG_FLASH_ADDR_L	0xf0
+#define REG_FLASH_ADDR_H	0xf1
+#define REG_FLASH_CRC8		0xf2
+
+#define REG_FLASH_UNLOCK	0xf3
+#define REG_FLASH_UNLOCK_MAGIC	0x46
+
+#define REG_FLASH_CMD		0xf4
+#define REG_FLASH_CMD_READ_ROM	0x52
+#define REG_FLASH_CMD_WRITE_ROM	0x57
+#define REG_FLASH_CMD_ERASE_ROM	0x45
+#define REG_FLASH_CMD_COMMIT	0x43
+
+#define REG_DEBUG_LOG		0xff
+
 /* default regmap from the factory */
 
 #define DEFAULT_MAP_ROWS 12
@@ -251,6 +303,135 @@ static irqreturn_t kb151_irq_thread(int irq, void *data)
 	return IRQ_HANDLED;
 }
 
+#ifdef CONFIG_IP5XXX_POWER
+
+static int kb151_charger_regmap_read(void *context,
+				     unsigned int reg, unsigned int *val)
+{
+	struct i2c_client *client = context;
+	int ret, i;
+
+	// initiate read of data from the charger
+	uint8_t buf[4] = { REG_SYS_CHG_ADDR, reg, 0xAA, REG_SYS_COMMAND_CHG_READ };
+	struct i2c_msg msgs[] = {
+		{ client->addr, 0, 4, buf },
+	};
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret < 0) {
+		dev_err(&client->dev, "Charger read failed (%d)\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < 5; i++) {
+		// read the result
+		uint8_t buf2[1] = { REG_SYS_CHG_DATA, };
+		uint8_t buf3[2] = { };
+		struct i2c_msg msgs2[] = {
+			{ client->addr, 0, 1, buf2 },
+			{ client->addr, I2C_M_RD, sizeof(buf3), buf3 },
+		};
+
+		usleep_range(700, 1200);
+
+		ret = i2c_transfer(client->adapter, msgs2, ARRAY_SIZE(msgs2));
+		if (ret < 0) {
+			dev_err(&client->dev, "Charger read failed (%d)\n", ret);
+			return ret;
+		}
+
+		dev_dbg(&client->dev, "rd %02x %02x\n", buf3[0], buf3[1]);
+
+		if (buf3[1] == REG_SYS_COMMAND_CHG_READ)
+			continue;
+
+		if (buf3[1] == 0) {
+			*val = buf3[0];
+			return 0;
+		}
+
+		if (buf3[1] == 0xff) {
+			dev_err(&client->dev,
+				"Charger read failed - MCU returned 0x%hhx\n",
+				buf3[1]);
+			return -EIO;
+		}
+	}
+
+	dev_err(&client->dev, "Charger read failed - timeout (%d)\n", ret);
+	return -ETIMEDOUT;
+}
+
+static int kb151_charger_regmap_write(void *context, unsigned int reg, unsigned int val)
+{
+	struct i2c_client *client = context;
+	int ret, i;
+
+	uint8_t buf[4] = { REG_SYS_CHG_ADDR, reg, val, REG_SYS_COMMAND_CHG_WRITE };
+	struct i2c_msg msgs[] = {
+		{ client->addr, 0, 4, buf },
+	};
+
+	dev_dbg(&client->dev, "wr 0x%02hhx: %02hhx\n", reg, val);
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret < 0) {
+		dev_err(&client->dev, "Charger write failed (%d)\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < 5; i++) {
+		// read the result
+		uint8_t buf2[1] = { REG_SYS_COMMAND, };
+		uint8_t buf3[1] = { };
+		struct i2c_msg msgs2[] = {
+			{ client->addr, 0, 1, buf2 },
+			{ client->addr, I2C_M_RD, sizeof(buf3), buf3 },
+		};
+
+		usleep_range(700, 1200);
+
+		ret = i2c_transfer(client->adapter, msgs2, ARRAY_SIZE(msgs2));
+		if (ret < 0) {
+			dev_err(&client->dev, "Charger write failed (%d)\n", ret);
+			return ret;
+		}
+
+		if (buf3[0] == REG_SYS_COMMAND_CHG_WRITE)
+			continue;
+
+		if (buf3[0] == 0)
+			return 0;
+
+		if (buf3[0] == 0xff) {
+			dev_err(&client->dev,
+				"Charger write failed - MCU returned 0x%hhx\n",
+				buf2[1]);
+			return -EIO;
+		}
+	}
+
+	dev_err(&client->dev, "Charger write failed - timeout (%d)\n", ret);
+	return -ETIMEDOUT;
+}
+
+static struct regmap_bus kb151_charger_regmap_bus = {
+	.reg_write = kb151_charger_regmap_write,
+	.reg_read = kb151_charger_regmap_read,
+};
+
+static struct regmap_config kb151_charger_regmap_config = {
+        .reg_bits       = 8,
+        .reg_stride     = 1,
+        .val_bits       = 8,
+        .max_register   = 0xff,
+        .cache_type     = REGCACHE_NONE,
+};
+
+extern int ip5xxx_power_probe_with_regmap(struct device* dev, struct regmap *regmap);
+
+#endif
+
 static int kb151_probe(struct i2c_client *client)
 {
 	struct device *dev = &client->dev;
@@ -258,6 +439,9 @@ static int kb151_probe(struct i2c_client *client)
 	unsigned int kb_rows, kb_cols;
 	unsigned int map_rows = DEFAULT_MAP_ROWS, map_cols = DEFAULT_MAP_COLS;
 	struct kb151 *kb151;
+#ifdef CONFIG_IP5XXX_POWER
+	struct regmap* regmap;
+#endif
 	bool has_of_keymap;
 	int ret;
 
@@ -339,6 +523,28 @@ static int kb151_probe(struct i2c_client *client)
 	if (ret)
 		return dev_err_probe(dev, ret, "Failed to register input\n");
 
+#ifdef CONFIG_IP5XXX_POWER
+	// we need to create a custom regmap_bus that will proxy
+	// charger register reads/writes via a keyboard MCU
+	regmap = __regmap_lockdep_wrapper(__devm_regmap_init,
+		 "kb151-charger-regmap", dev,
+		  &kb151_charger_regmap_bus, client,
+		  &kb151_charger_regmap_config);
+	if (IS_ERR(regmap)) {
+		dev_err(dev, "Failed to create charger regmap (%ld)\n", PTR_ERR(regmap));
+		return PTR_ERR(regmap);
+	}
+
+	// initialize the charger
+	ret = ip5xxx_power_probe_with_regmap(dev, regmap);
+	if (ret) {
+		dev_err(dev, "Failed to initialize keyboard charger (%d)\n", ret);
+		return ret;
+	}
+#else
+	dev_warn(dev, "Your kernel doesn't have CONFIG_IP5XXX_POWER enabled, keyboard charger support is disabled.\n");
+#endif
+
 	return 0;
 }
 
-- 
2.31.1

