18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (C) 2020 Marek Vasut <marex@denx.de>
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Based on rpi_touchscreen.c by Eric Anholt <eric@anholt.net>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/backlight.h>
98c2ecf20Sopenharmony_ci#include <linux/err.h>
108c2ecf20Sopenharmony_ci#include <linux/gpio.h>
118c2ecf20Sopenharmony_ci#include <linux/i2c.h>
128c2ecf20Sopenharmony_ci#include <linux/init.h>
138c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
148c2ecf20Sopenharmony_ci#include <linux/module.h>
158c2ecf20Sopenharmony_ci#include <linux/regmap.h>
168c2ecf20Sopenharmony_ci#include <linux/regulator/driver.h>
178c2ecf20Sopenharmony_ci#include <linux/regulator/machine.h>
188c2ecf20Sopenharmony_ci#include <linux/regulator/of_regulator.h>
198c2ecf20Sopenharmony_ci#include <linux/slab.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci/* I2C registers of the Atmel microcontroller. */
228c2ecf20Sopenharmony_ci#define REG_ID		0x80
238c2ecf20Sopenharmony_ci#define REG_PORTA	0x81
248c2ecf20Sopenharmony_ci#define REG_PORTA_HF	BIT(2)
258c2ecf20Sopenharmony_ci#define REG_PORTA_VF	BIT(3)
268c2ecf20Sopenharmony_ci#define REG_PORTB	0x82
278c2ecf20Sopenharmony_ci#define REG_POWERON	0x85
288c2ecf20Sopenharmony_ci#define REG_PWM		0x86
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_cistatic const struct regmap_config attiny_regmap_config = {
318c2ecf20Sopenharmony_ci	.reg_bits = 8,
328c2ecf20Sopenharmony_ci	.val_bits = 8,
338c2ecf20Sopenharmony_ci	.max_register = REG_PWM,
348c2ecf20Sopenharmony_ci	.cache_type = REGCACHE_NONE,
358c2ecf20Sopenharmony_ci};
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_cistatic int attiny_lcd_power_enable(struct regulator_dev *rdev)
388c2ecf20Sopenharmony_ci{
398c2ecf20Sopenharmony_ci	unsigned int data;
408c2ecf20Sopenharmony_ci	int ret, i;
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	regmap_write(rdev->regmap, REG_POWERON, 1);
438c2ecf20Sopenharmony_ci	msleep(80);
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci	/* Wait for nPWRDWN to go low to indicate poweron is done. */
468c2ecf20Sopenharmony_ci	for (i = 0; i < 20; i++) {
478c2ecf20Sopenharmony_ci		ret = regmap_read(rdev->regmap, REG_PORTB, &data);
488c2ecf20Sopenharmony_ci		if (!ret) {
498c2ecf20Sopenharmony_ci			if (data & BIT(0))
508c2ecf20Sopenharmony_ci				break;
518c2ecf20Sopenharmony_ci		}
528c2ecf20Sopenharmony_ci		usleep_range(10000, 12000);
538c2ecf20Sopenharmony_ci	}
548c2ecf20Sopenharmony_ci	usleep_range(10000, 12000);
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	if (ret)
578c2ecf20Sopenharmony_ci		pr_err("%s: regmap_read_poll_timeout failed %d\n", __func__, ret);
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci	/* Default to the same orientation as the closed source
608c2ecf20Sopenharmony_ci	 * firmware used for the panel.  Runtime rotation
618c2ecf20Sopenharmony_ci	 * configuration will be supported using VC4's plane
628c2ecf20Sopenharmony_ci	 * orientation bits.
638c2ecf20Sopenharmony_ci	 */
648c2ecf20Sopenharmony_ci	regmap_write(rdev->regmap, REG_PORTA, BIT(2));
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	return 0;
678c2ecf20Sopenharmony_ci}
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistatic int attiny_lcd_power_disable(struct regulator_dev *rdev)
708c2ecf20Sopenharmony_ci{
718c2ecf20Sopenharmony_ci	regmap_write(rdev->regmap, REG_PWM, 0);
728c2ecf20Sopenharmony_ci	regmap_write(rdev->regmap, REG_POWERON, 0);
738c2ecf20Sopenharmony_ci	msleep(30);
748c2ecf20Sopenharmony_ci	return 0;
758c2ecf20Sopenharmony_ci}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_cistatic int attiny_lcd_power_is_enabled(struct regulator_dev *rdev)
788c2ecf20Sopenharmony_ci{
798c2ecf20Sopenharmony_ci	unsigned int data;
808c2ecf20Sopenharmony_ci	int ret, i;
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	for (i = 0; i < 10; i++) {
838c2ecf20Sopenharmony_ci		ret = regmap_read(rdev->regmap, REG_POWERON, &data);
848c2ecf20Sopenharmony_ci		if (!ret)
858c2ecf20Sopenharmony_ci			break;
868c2ecf20Sopenharmony_ci		usleep_range(10000, 12000);
878c2ecf20Sopenharmony_ci	}
888c2ecf20Sopenharmony_ci	if (ret < 0)
898c2ecf20Sopenharmony_ci		return ret;
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	if (!(data & BIT(0)))
928c2ecf20Sopenharmony_ci		return 0;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	for (i = 0; i < 10; i++) {
958c2ecf20Sopenharmony_ci		ret = regmap_read(rdev->regmap, REG_PORTB, &data);
968c2ecf20Sopenharmony_ci		if (!ret)
978c2ecf20Sopenharmony_ci			break;
988c2ecf20Sopenharmony_ci		usleep_range(10000, 12000);
998c2ecf20Sopenharmony_ci	}
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	if (ret < 0)
1028c2ecf20Sopenharmony_ci		return ret;
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	return data & BIT(0);
1058c2ecf20Sopenharmony_ci}
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_cistatic const struct regulator_init_data attiny_regulator_default = {
1088c2ecf20Sopenharmony_ci	.constraints = {
1098c2ecf20Sopenharmony_ci		.valid_ops_mask = REGULATOR_CHANGE_STATUS,
1108c2ecf20Sopenharmony_ci	},
1118c2ecf20Sopenharmony_ci};
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_cistatic const struct regulator_ops attiny_regulator_ops = {
1148c2ecf20Sopenharmony_ci	.enable = attiny_lcd_power_enable,
1158c2ecf20Sopenharmony_ci	.disable = attiny_lcd_power_disable,
1168c2ecf20Sopenharmony_ci	.is_enabled = attiny_lcd_power_is_enabled,
1178c2ecf20Sopenharmony_ci};
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_cistatic const struct regulator_desc attiny_regulator = {
1208c2ecf20Sopenharmony_ci	.name	= "tc358762-power",
1218c2ecf20Sopenharmony_ci	.ops	= &attiny_regulator_ops,
1228c2ecf20Sopenharmony_ci	.type	= REGULATOR_VOLTAGE,
1238c2ecf20Sopenharmony_ci	.owner	= THIS_MODULE,
1248c2ecf20Sopenharmony_ci};
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_cistatic int attiny_update_status(struct backlight_device *bl)
1278c2ecf20Sopenharmony_ci{
1288c2ecf20Sopenharmony_ci	struct regmap *regmap = bl_get_data(bl);
1298c2ecf20Sopenharmony_ci	int brightness = bl->props.brightness;
1308c2ecf20Sopenharmony_ci	int ret, i;
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	if (bl->props.power != FB_BLANK_UNBLANK ||
1338c2ecf20Sopenharmony_ci	    bl->props.fb_blank != FB_BLANK_UNBLANK)
1348c2ecf20Sopenharmony_ci		brightness = 0;
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	for (i = 0; i < 10; i++) {
1378c2ecf20Sopenharmony_ci		ret = regmap_write(regmap, REG_PWM, brightness);
1388c2ecf20Sopenharmony_ci		if (!ret)
1398c2ecf20Sopenharmony_ci			break;
1408c2ecf20Sopenharmony_ci	}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	return ret;
1438c2ecf20Sopenharmony_ci}
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_cistatic int attiny_get_brightness(struct backlight_device *bl)
1468c2ecf20Sopenharmony_ci{
1478c2ecf20Sopenharmony_ci	struct regmap *regmap = bl_get_data(bl);
1488c2ecf20Sopenharmony_ci	int ret, brightness, i;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	for (i = 0; i < 10; i++) {
1518c2ecf20Sopenharmony_ci		ret = regmap_read(regmap, REG_PWM, &brightness);
1528c2ecf20Sopenharmony_ci		if (!ret)
1538c2ecf20Sopenharmony_ci			break;
1548c2ecf20Sopenharmony_ci	}
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	if (ret)
1578c2ecf20Sopenharmony_ci		return ret;
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	return brightness;
1608c2ecf20Sopenharmony_ci}
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_cistatic const struct backlight_ops attiny_bl = {
1638c2ecf20Sopenharmony_ci	.update_status	= attiny_update_status,
1648c2ecf20Sopenharmony_ci	.get_brightness	= attiny_get_brightness,
1658c2ecf20Sopenharmony_ci};
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci/*
1688c2ecf20Sopenharmony_ci * I2C driver interface functions
1698c2ecf20Sopenharmony_ci */
1708c2ecf20Sopenharmony_cistatic int attiny_i2c_probe(struct i2c_client *i2c,
1718c2ecf20Sopenharmony_ci		const struct i2c_device_id *id)
1728c2ecf20Sopenharmony_ci{
1738c2ecf20Sopenharmony_ci	struct backlight_properties props = { };
1748c2ecf20Sopenharmony_ci	struct regulator_config config = { };
1758c2ecf20Sopenharmony_ci	struct backlight_device *bl;
1768c2ecf20Sopenharmony_ci	struct regulator_dev *rdev;
1778c2ecf20Sopenharmony_ci	struct regmap *regmap;
1788c2ecf20Sopenharmony_ci	unsigned int data;
1798c2ecf20Sopenharmony_ci	int ret;
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	regmap = devm_regmap_init_i2c(i2c, &attiny_regmap_config);
1828c2ecf20Sopenharmony_ci	if (IS_ERR(regmap)) {
1838c2ecf20Sopenharmony_ci		ret = PTR_ERR(regmap);
1848c2ecf20Sopenharmony_ci		dev_err(&i2c->dev, "Failed to allocate register map: %d\n",
1858c2ecf20Sopenharmony_ci			ret);
1868c2ecf20Sopenharmony_ci		return ret;
1878c2ecf20Sopenharmony_ci	}
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	ret = regmap_read(regmap, REG_ID, &data);
1908c2ecf20Sopenharmony_ci	if (ret < 0) {
1918c2ecf20Sopenharmony_ci		dev_err(&i2c->dev, "Failed to read REG_ID reg: %d\n", ret);
1928c2ecf20Sopenharmony_ci		return ret;
1938c2ecf20Sopenharmony_ci	}
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	switch (data) {
1968c2ecf20Sopenharmony_ci	case 0xde: /* ver 1 */
1978c2ecf20Sopenharmony_ci	case 0xc3: /* ver 2 */
1988c2ecf20Sopenharmony_ci		break;
1998c2ecf20Sopenharmony_ci	default:
2008c2ecf20Sopenharmony_ci		dev_err(&i2c->dev, "Unknown Atmel firmware revision: 0x%02x\n", data);
2018c2ecf20Sopenharmony_ci		return -ENODEV;
2028c2ecf20Sopenharmony_ci	}
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	regmap_write(regmap, REG_POWERON, 0);
2058c2ecf20Sopenharmony_ci	msleep(30);
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	config.dev = &i2c->dev;
2088c2ecf20Sopenharmony_ci	config.regmap = regmap;
2098c2ecf20Sopenharmony_ci	config.of_node = i2c->dev.of_node;
2108c2ecf20Sopenharmony_ci	config.init_data = &attiny_regulator_default;
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	rdev = devm_regulator_register(&i2c->dev, &attiny_regulator, &config);
2138c2ecf20Sopenharmony_ci	if (IS_ERR(rdev)) {
2148c2ecf20Sopenharmony_ci		dev_err(&i2c->dev, "Failed to register ATTINY regulator\n");
2158c2ecf20Sopenharmony_ci		return PTR_ERR(rdev);
2168c2ecf20Sopenharmony_ci	}
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	props.type = BACKLIGHT_RAW;
2198c2ecf20Sopenharmony_ci	props.max_brightness = 0xff;
2208c2ecf20Sopenharmony_ci	bl = devm_backlight_device_register(&i2c->dev,
2218c2ecf20Sopenharmony_ci					    "7inch-touchscreen-panel-bl",
2228c2ecf20Sopenharmony_ci					    &i2c->dev, regmap, &attiny_bl,
2238c2ecf20Sopenharmony_ci					    &props);
2248c2ecf20Sopenharmony_ci	if (IS_ERR(bl))
2258c2ecf20Sopenharmony_ci		return PTR_ERR(bl);
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	bl->props.brightness = 0xff;
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	return 0;
2308c2ecf20Sopenharmony_ci}
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_cistatic const struct of_device_id attiny_dt_ids[] = {
2338c2ecf20Sopenharmony_ci	{ .compatible = "raspberrypi,7inch-touchscreen-panel-regulator" },
2348c2ecf20Sopenharmony_ci	{},
2358c2ecf20Sopenharmony_ci};
2368c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, attiny_dt_ids);
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_cistatic struct i2c_driver attiny_regulator_driver = {
2398c2ecf20Sopenharmony_ci	.driver = {
2408c2ecf20Sopenharmony_ci		.name = "rpi_touchscreen_attiny",
2418c2ecf20Sopenharmony_ci		.of_match_table = of_match_ptr(attiny_dt_ids),
2428c2ecf20Sopenharmony_ci	},
2438c2ecf20Sopenharmony_ci	.probe = attiny_i2c_probe,
2448c2ecf20Sopenharmony_ci};
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_cimodule_i2c_driver(attiny_regulator_driver);
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ciMODULE_AUTHOR("Marek Vasut <marex@denx.de>");
2498c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Regulator device driver for Raspberry Pi 7-inch touchscreen");
2508c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
251