1// SPDX-License-Identifier: GPL-2.0 2/* 3 * Copyright (C) 2020 Marek Vasut <marex@denx.de> 4 * 5 * Based on rpi_touchscreen.c by Eric Anholt <eric@anholt.net> 6 */ 7 8#include <linux/backlight.h> 9#include <linux/err.h> 10#include <linux/gpio.h> 11#include <linux/i2c.h> 12#include <linux/init.h> 13#include <linux/interrupt.h> 14#include <linux/module.h> 15#include <linux/regmap.h> 16#include <linux/regulator/driver.h> 17#include <linux/regulator/machine.h> 18#include <linux/regulator/of_regulator.h> 19#include <linux/slab.h> 20 21/* I2C registers of the Atmel microcontroller. */ 22#define REG_ID 0x80 23#define REG_PORTA 0x81 24#define REG_PORTA_HF BIT(2) 25#define REG_PORTA_VF BIT(3) 26#define REG_PORTB 0x82 27#define REG_POWERON 0x85 28#define REG_PWM 0x86 29 30static const struct regmap_config attiny_regmap_config = { 31 .reg_bits = 8, 32 .val_bits = 8, 33 .max_register = REG_PWM, 34 .cache_type = REGCACHE_NONE, 35}; 36 37static int attiny_lcd_power_enable(struct regulator_dev *rdev) 38{ 39 unsigned int data; 40 int ret, i; 41 42 regmap_write(rdev->regmap, REG_POWERON, 1); 43 msleep(80); 44 45 /* Wait for nPWRDWN to go low to indicate poweron is done. */ 46 for (i = 0; i < 20; i++) { 47 ret = regmap_read(rdev->regmap, REG_PORTB, &data); 48 if (!ret) { 49 if (data & BIT(0)) 50 break; 51 } 52 usleep_range(10000, 12000); 53 } 54 usleep_range(10000, 12000); 55 56 if (ret) 57 pr_err("%s: regmap_read_poll_timeout failed %d\n", __func__, ret); 58 59 /* Default to the same orientation as the closed source 60 * firmware used for the panel. Runtime rotation 61 * configuration will be supported using VC4's plane 62 * orientation bits. 63 */ 64 regmap_write(rdev->regmap, REG_PORTA, BIT(2)); 65 66 return 0; 67} 68 69static int attiny_lcd_power_disable(struct regulator_dev *rdev) 70{ 71 regmap_write(rdev->regmap, REG_PWM, 0); 72 regmap_write(rdev->regmap, REG_POWERON, 0); 73 msleep(30); 74 return 0; 75} 76 77static int attiny_lcd_power_is_enabled(struct regulator_dev *rdev) 78{ 79 unsigned int data; 80 int ret, i; 81 82 for (i = 0; i < 10; i++) { 83 ret = regmap_read(rdev->regmap, REG_POWERON, &data); 84 if (!ret) 85 break; 86 usleep_range(10000, 12000); 87 } 88 if (ret < 0) 89 return ret; 90 91 if (!(data & BIT(0))) 92 return 0; 93 94 for (i = 0; i < 10; i++) { 95 ret = regmap_read(rdev->regmap, REG_PORTB, &data); 96 if (!ret) 97 break; 98 usleep_range(10000, 12000); 99 } 100 101 if (ret < 0) 102 return ret; 103 104 return data & BIT(0); 105} 106 107static const struct regulator_init_data attiny_regulator_default = { 108 .constraints = { 109 .valid_ops_mask = REGULATOR_CHANGE_STATUS, 110 }, 111}; 112 113static const struct regulator_ops attiny_regulator_ops = { 114 .enable = attiny_lcd_power_enable, 115 .disable = attiny_lcd_power_disable, 116 .is_enabled = attiny_lcd_power_is_enabled, 117}; 118 119static const struct regulator_desc attiny_regulator = { 120 .name = "tc358762-power", 121 .ops = &attiny_regulator_ops, 122 .type = REGULATOR_VOLTAGE, 123 .owner = THIS_MODULE, 124}; 125 126static int attiny_update_status(struct backlight_device *bl) 127{ 128 struct regmap *regmap = bl_get_data(bl); 129 int brightness = bl->props.brightness; 130 int ret, i; 131 132 if (bl->props.power != FB_BLANK_UNBLANK || 133 bl->props.fb_blank != FB_BLANK_UNBLANK) 134 brightness = 0; 135 136 for (i = 0; i < 10; i++) { 137 ret = regmap_write(regmap, REG_PWM, brightness); 138 if (!ret) 139 break; 140 } 141 142 return ret; 143} 144 145static int attiny_get_brightness(struct backlight_device *bl) 146{ 147 struct regmap *regmap = bl_get_data(bl); 148 int ret, brightness, i; 149 150 for (i = 0; i < 10; i++) { 151 ret = regmap_read(regmap, REG_PWM, &brightness); 152 if (!ret) 153 break; 154 } 155 156 if (ret) 157 return ret; 158 159 return brightness; 160} 161 162static const struct backlight_ops attiny_bl = { 163 .update_status = attiny_update_status, 164 .get_brightness = attiny_get_brightness, 165}; 166 167/* 168 * I2C driver interface functions 169 */ 170static int attiny_i2c_probe(struct i2c_client *i2c, 171 const struct i2c_device_id *id) 172{ 173 struct backlight_properties props = { }; 174 struct regulator_config config = { }; 175 struct backlight_device *bl; 176 struct regulator_dev *rdev; 177 struct regmap *regmap; 178 unsigned int data; 179 int ret; 180 181 regmap = devm_regmap_init_i2c(i2c, &attiny_regmap_config); 182 if (IS_ERR(regmap)) { 183 ret = PTR_ERR(regmap); 184 dev_err(&i2c->dev, "Failed to allocate register map: %d\n", 185 ret); 186 return ret; 187 } 188 189 ret = regmap_read(regmap, REG_ID, &data); 190 if (ret < 0) { 191 dev_err(&i2c->dev, "Failed to read REG_ID reg: %d\n", ret); 192 return ret; 193 } 194 195 switch (data) { 196 case 0xde: /* ver 1 */ 197 case 0xc3: /* ver 2 */ 198 break; 199 default: 200 dev_err(&i2c->dev, "Unknown Atmel firmware revision: 0x%02x\n", data); 201 return -ENODEV; 202 } 203 204 regmap_write(regmap, REG_POWERON, 0); 205 msleep(30); 206 207 config.dev = &i2c->dev; 208 config.regmap = regmap; 209 config.of_node = i2c->dev.of_node; 210 config.init_data = &attiny_regulator_default; 211 212 rdev = devm_regulator_register(&i2c->dev, &attiny_regulator, &config); 213 if (IS_ERR(rdev)) { 214 dev_err(&i2c->dev, "Failed to register ATTINY regulator\n"); 215 return PTR_ERR(rdev); 216 } 217 218 props.type = BACKLIGHT_RAW; 219 props.max_brightness = 0xff; 220 bl = devm_backlight_device_register(&i2c->dev, 221 "7inch-touchscreen-panel-bl", 222 &i2c->dev, regmap, &attiny_bl, 223 &props); 224 if (IS_ERR(bl)) 225 return PTR_ERR(bl); 226 227 bl->props.brightness = 0xff; 228 229 return 0; 230} 231 232static const struct of_device_id attiny_dt_ids[] = { 233 { .compatible = "raspberrypi,7inch-touchscreen-panel-regulator" }, 234 {}, 235}; 236MODULE_DEVICE_TABLE(of, attiny_dt_ids); 237 238static struct i2c_driver attiny_regulator_driver = { 239 .driver = { 240 .name = "rpi_touchscreen_attiny", 241 .of_match_table = of_match_ptr(attiny_dt_ids), 242 }, 243 .probe = attiny_i2c_probe, 244}; 245 246module_i2c_driver(attiny_regulator_driver); 247 248MODULE_AUTHOR("Marek Vasut <marex@denx.de>"); 249MODULE_DESCRIPTION("Regulator device driver for Raspberry Pi 7-inch touchscreen"); 250MODULE_LICENSE("GPL v2"); 251