138573472SMarek Vasut // SPDX-License-Identifier: GPL-2.0 238573472SMarek Vasut /* 338573472SMarek Vasut * Copyright (C) 2020 Marek Vasut <marex@denx.de> 438573472SMarek Vasut * 538573472SMarek Vasut * Based on rpi_touchscreen.c by Eric Anholt <eric@anholt.net> 638573472SMarek Vasut */ 738573472SMarek Vasut 838573472SMarek Vasut #include <linux/backlight.h> 938573472SMarek Vasut #include <linux/err.h> 1038573472SMarek Vasut #include <linux/gpio.h> 1138573472SMarek Vasut #include <linux/i2c.h> 1238573472SMarek Vasut #include <linux/init.h> 1338573472SMarek Vasut #include <linux/interrupt.h> 1438573472SMarek Vasut #include <linux/module.h> 1538573472SMarek Vasut #include <linux/regmap.h> 1638573472SMarek Vasut #include <linux/regulator/driver.h> 1738573472SMarek Vasut #include <linux/regulator/machine.h> 1838573472SMarek Vasut #include <linux/regulator/of_regulator.h> 1938573472SMarek Vasut #include <linux/slab.h> 2038573472SMarek Vasut 2138573472SMarek Vasut /* I2C registers of the Atmel microcontroller. */ 2238573472SMarek Vasut #define REG_ID 0x80 2338573472SMarek Vasut #define REG_PORTA 0x81 2438573472SMarek Vasut #define REG_PORTA_HF BIT(2) 2538573472SMarek Vasut #define REG_PORTA_VF BIT(3) 2638573472SMarek Vasut #define REG_PORTB 0x82 2738573472SMarek Vasut #define REG_POWERON 0x85 2838573472SMarek Vasut #define REG_PWM 0x86 2938573472SMarek Vasut 3000440bcdSDave Stevenson struct attiny_lcd { 3100440bcdSDave Stevenson /* lock to serialise overall accesses to the Atmel */ 3200440bcdSDave Stevenson struct mutex lock; 3300440bcdSDave Stevenson struct regmap *regmap; 3400440bcdSDave Stevenson }; 3500440bcdSDave Stevenson 3638573472SMarek Vasut static const struct regmap_config attiny_regmap_config = { 3738573472SMarek Vasut .reg_bits = 8, 3838573472SMarek Vasut .val_bits = 8, 3900440bcdSDave Stevenson .disable_locking = 1, 4038573472SMarek Vasut .max_register = REG_PWM, 4138573472SMarek Vasut .cache_type = REGCACHE_NONE, 4238573472SMarek Vasut }; 4338573472SMarek Vasut 4438573472SMarek Vasut static int attiny_lcd_power_enable(struct regulator_dev *rdev) 4538573472SMarek Vasut { 4600440bcdSDave Stevenson struct mutex *lock = rdev_get_drvdata(rdev); 4738573472SMarek Vasut unsigned int data; 485665eee7SDave Stevenson int ret, i; 4938573472SMarek Vasut 5000440bcdSDave Stevenson mutex_lock(lock); 5100440bcdSDave Stevenson 5238573472SMarek Vasut regmap_write(rdev->regmap, REG_POWERON, 1); 535665eee7SDave Stevenson msleep(80); 545665eee7SDave Stevenson 5538573472SMarek Vasut /* Wait for nPWRDWN to go low to indicate poweron is done. */ 565665eee7SDave Stevenson for (i = 0; i < 20; i++) { 575665eee7SDave Stevenson ret = regmap_read(rdev->regmap, REG_PORTB, &data); 585665eee7SDave Stevenson if (!ret) { 595665eee7SDave Stevenson if (data & BIT(0)) 605665eee7SDave Stevenson break; 615665eee7SDave Stevenson } 625665eee7SDave Stevenson usleep_range(10000, 12000); 635665eee7SDave Stevenson } 645665eee7SDave Stevenson usleep_range(10000, 12000); 655665eee7SDave Stevenson 665665eee7SDave Stevenson if (ret) 675665eee7SDave Stevenson pr_err("%s: regmap_read_poll_timeout failed %d\n", __func__, ret); 6838573472SMarek Vasut 6938573472SMarek Vasut /* Default to the same orientation as the closed source 7038573472SMarek Vasut * firmware used for the panel. Runtime rotation 7138573472SMarek Vasut * configuration will be supported using VC4's plane 7238573472SMarek Vasut * orientation bits. 7338573472SMarek Vasut */ 7438573472SMarek Vasut regmap_write(rdev->regmap, REG_PORTA, BIT(2)); 7538573472SMarek Vasut 7600440bcdSDave Stevenson mutex_unlock(lock); 7700440bcdSDave Stevenson 7838573472SMarek Vasut return 0; 7938573472SMarek Vasut } 8038573472SMarek Vasut 8138573472SMarek Vasut static int attiny_lcd_power_disable(struct regulator_dev *rdev) 8238573472SMarek Vasut { 8300440bcdSDave Stevenson struct mutex *lock = rdev_get_drvdata(rdev); 8400440bcdSDave Stevenson 8500440bcdSDave Stevenson mutex_lock(lock); 8600440bcdSDave Stevenson 8738573472SMarek Vasut regmap_write(rdev->regmap, REG_PWM, 0); 8838573472SMarek Vasut regmap_write(rdev->regmap, REG_POWERON, 0); 895665eee7SDave Stevenson msleep(30); 9000440bcdSDave Stevenson 9100440bcdSDave Stevenson mutex_unlock(lock); 9200440bcdSDave Stevenson 9338573472SMarek Vasut return 0; 9438573472SMarek Vasut } 9538573472SMarek Vasut 9638573472SMarek Vasut static int attiny_lcd_power_is_enabled(struct regulator_dev *rdev) 9738573472SMarek Vasut { 9800440bcdSDave Stevenson struct mutex *lock = rdev_get_drvdata(rdev); 9938573472SMarek Vasut unsigned int data; 1005665eee7SDave Stevenson int ret, i; 10138573472SMarek Vasut 10200440bcdSDave Stevenson mutex_lock(lock); 10300440bcdSDave Stevenson 1045665eee7SDave Stevenson for (i = 0; i < 10; i++) { 10538573472SMarek Vasut ret = regmap_read(rdev->regmap, REG_POWERON, &data); 1065665eee7SDave Stevenson if (!ret) 1075665eee7SDave Stevenson break; 1085665eee7SDave Stevenson usleep_range(10000, 12000); 1095665eee7SDave Stevenson } 11000440bcdSDave Stevenson if (ret < 0) { 11100440bcdSDave Stevenson mutex_unlock(lock); 11238573472SMarek Vasut return ret; 11300440bcdSDave Stevenson } 11438573472SMarek Vasut 11500440bcdSDave Stevenson if (!(data & BIT(0))) { 11600440bcdSDave Stevenson mutex_unlock(lock); 11738573472SMarek Vasut return 0; 11800440bcdSDave Stevenson } 11938573472SMarek Vasut 1205665eee7SDave Stevenson for (i = 0; i < 10; i++) { 12138573472SMarek Vasut ret = regmap_read(rdev->regmap, REG_PORTB, &data); 1225665eee7SDave Stevenson if (!ret) 1235665eee7SDave Stevenson break; 1245665eee7SDave Stevenson usleep_range(10000, 12000); 1255665eee7SDave Stevenson } 1265665eee7SDave Stevenson 12700440bcdSDave Stevenson mutex_unlock(lock); 12800440bcdSDave Stevenson 12938573472SMarek Vasut if (ret < 0) 13038573472SMarek Vasut return ret; 13138573472SMarek Vasut 13238573472SMarek Vasut return data & BIT(0); 13338573472SMarek Vasut } 13438573472SMarek Vasut 13538573472SMarek Vasut static const struct regulator_init_data attiny_regulator_default = { 13638573472SMarek Vasut .constraints = { 13738573472SMarek Vasut .valid_ops_mask = REGULATOR_CHANGE_STATUS, 13838573472SMarek Vasut }, 13938573472SMarek Vasut }; 14038573472SMarek Vasut 14138573472SMarek Vasut static const struct regulator_ops attiny_regulator_ops = { 14238573472SMarek Vasut .enable = attiny_lcd_power_enable, 14338573472SMarek Vasut .disable = attiny_lcd_power_disable, 14438573472SMarek Vasut .is_enabled = attiny_lcd_power_is_enabled, 14538573472SMarek Vasut }; 14638573472SMarek Vasut 14738573472SMarek Vasut static const struct regulator_desc attiny_regulator = { 14838573472SMarek Vasut .name = "tc358762-power", 14938573472SMarek Vasut .ops = &attiny_regulator_ops, 15038573472SMarek Vasut .type = REGULATOR_VOLTAGE, 15138573472SMarek Vasut .owner = THIS_MODULE, 15238573472SMarek Vasut }; 15338573472SMarek Vasut 15438573472SMarek Vasut static int attiny_update_status(struct backlight_device *bl) 15538573472SMarek Vasut { 15600440bcdSDave Stevenson struct attiny_lcd *state = bl_get_data(bl); 15700440bcdSDave Stevenson struct regmap *regmap = state->regmap; 15838573472SMarek Vasut int brightness = bl->props.brightness; 1595665eee7SDave Stevenson int ret, i; 16038573472SMarek Vasut 16100440bcdSDave Stevenson mutex_lock(&state->lock); 16200440bcdSDave Stevenson 16338573472SMarek Vasut if (bl->props.power != FB_BLANK_UNBLANK || 16438573472SMarek Vasut bl->props.fb_blank != FB_BLANK_UNBLANK) 16538573472SMarek Vasut brightness = 0; 16638573472SMarek Vasut 1675665eee7SDave Stevenson for (i = 0; i < 10; i++) { 1685665eee7SDave Stevenson ret = regmap_write(regmap, REG_PWM, brightness); 1695665eee7SDave Stevenson if (!ret) 1705665eee7SDave Stevenson break; 1715665eee7SDave Stevenson } 1725665eee7SDave Stevenson 17300440bcdSDave Stevenson mutex_unlock(&state->lock); 17400440bcdSDave Stevenson 1755665eee7SDave Stevenson return ret; 17638573472SMarek Vasut } 17738573472SMarek Vasut 17838573472SMarek Vasut static int attiny_get_brightness(struct backlight_device *bl) 17938573472SMarek Vasut { 18000440bcdSDave Stevenson struct attiny_lcd *state = bl_get_data(bl); 18100440bcdSDave Stevenson struct regmap *regmap = state->regmap; 1825665eee7SDave Stevenson int ret, brightness, i; 18338573472SMarek Vasut 18400440bcdSDave Stevenson mutex_lock(&state->lock); 18500440bcdSDave Stevenson 1865665eee7SDave Stevenson for (i = 0; i < 10; i++) { 18738573472SMarek Vasut ret = regmap_read(regmap, REG_PWM, &brightness); 1885665eee7SDave Stevenson if (!ret) 1895665eee7SDave Stevenson break; 1905665eee7SDave Stevenson } 1915665eee7SDave Stevenson 19200440bcdSDave Stevenson mutex_unlock(&state->lock); 19300440bcdSDave Stevenson 19438573472SMarek Vasut if (ret) 19538573472SMarek Vasut return ret; 19638573472SMarek Vasut 19738573472SMarek Vasut return brightness; 19838573472SMarek Vasut } 19938573472SMarek Vasut 20038573472SMarek Vasut static const struct backlight_ops attiny_bl = { 20138573472SMarek Vasut .update_status = attiny_update_status, 20238573472SMarek Vasut .get_brightness = attiny_get_brightness, 20338573472SMarek Vasut }; 20438573472SMarek Vasut 20538573472SMarek Vasut /* 20638573472SMarek Vasut * I2C driver interface functions 20738573472SMarek Vasut */ 20838573472SMarek Vasut static int attiny_i2c_probe(struct i2c_client *i2c, 20938573472SMarek Vasut const struct i2c_device_id *id) 21038573472SMarek Vasut { 21138573472SMarek Vasut struct backlight_properties props = { }; 21238573472SMarek Vasut struct regulator_config config = { }; 21338573472SMarek Vasut struct backlight_device *bl; 21438573472SMarek Vasut struct regulator_dev *rdev; 21500440bcdSDave Stevenson struct attiny_lcd *state; 21638573472SMarek Vasut struct regmap *regmap; 21738573472SMarek Vasut unsigned int data; 21838573472SMarek Vasut int ret; 21938573472SMarek Vasut 22000440bcdSDave Stevenson state = devm_kzalloc(&i2c->dev, sizeof(*state), GFP_KERNEL); 22100440bcdSDave Stevenson if (!state) 22200440bcdSDave Stevenson return -ENOMEM; 22300440bcdSDave Stevenson 22400440bcdSDave Stevenson mutex_init(&state->lock); 22500440bcdSDave Stevenson i2c_set_clientdata(i2c, state); 22600440bcdSDave Stevenson 22738573472SMarek Vasut regmap = devm_regmap_init_i2c(i2c, &attiny_regmap_config); 22838573472SMarek Vasut if (IS_ERR(regmap)) { 22938573472SMarek Vasut ret = PTR_ERR(regmap); 23038573472SMarek Vasut dev_err(&i2c->dev, "Failed to allocate register map: %d\n", 23138573472SMarek Vasut ret); 23200440bcdSDave Stevenson goto error; 23338573472SMarek Vasut } 23438573472SMarek Vasut 23538573472SMarek Vasut ret = regmap_read(regmap, REG_ID, &data); 23638573472SMarek Vasut if (ret < 0) { 23738573472SMarek Vasut dev_err(&i2c->dev, "Failed to read REG_ID reg: %d\n", ret); 23800440bcdSDave Stevenson goto error; 23938573472SMarek Vasut } 24038573472SMarek Vasut 24138573472SMarek Vasut switch (data) { 24238573472SMarek Vasut case 0xde: /* ver 1 */ 24338573472SMarek Vasut case 0xc3: /* ver 2 */ 24438573472SMarek Vasut break; 24538573472SMarek Vasut default: 24638573472SMarek Vasut dev_err(&i2c->dev, "Unknown Atmel firmware revision: 0x%02x\n", data); 24700440bcdSDave Stevenson ret = -ENODEV; 24800440bcdSDave Stevenson goto error; 24938573472SMarek Vasut } 25038573472SMarek Vasut 25138573472SMarek Vasut regmap_write(regmap, REG_POWERON, 0); 2525665eee7SDave Stevenson msleep(30); 253*89339a2aSDave Stevenson regmap_write(regmap, REG_PWM, 0); 25438573472SMarek Vasut 25538573472SMarek Vasut config.dev = &i2c->dev; 25638573472SMarek Vasut config.regmap = regmap; 25738573472SMarek Vasut config.of_node = i2c->dev.of_node; 25838573472SMarek Vasut config.init_data = &attiny_regulator_default; 25900440bcdSDave Stevenson config.driver_data = &state->lock; 26038573472SMarek Vasut 26138573472SMarek Vasut rdev = devm_regulator_register(&i2c->dev, &attiny_regulator, &config); 26238573472SMarek Vasut if (IS_ERR(rdev)) { 26338573472SMarek Vasut dev_err(&i2c->dev, "Failed to register ATTINY regulator\n"); 26400440bcdSDave Stevenson ret = PTR_ERR(rdev); 26500440bcdSDave Stevenson goto error; 26638573472SMarek Vasut } 26738573472SMarek Vasut 26838573472SMarek Vasut props.type = BACKLIGHT_RAW; 26938573472SMarek Vasut props.max_brightness = 0xff; 27000440bcdSDave Stevenson 27100440bcdSDave Stevenson state->regmap = regmap; 27200440bcdSDave Stevenson 2737291e7d6SDave Stevenson bl = devm_backlight_device_register(&i2c->dev, dev_name(&i2c->dev), 27400440bcdSDave Stevenson &i2c->dev, state, &attiny_bl, 27538573472SMarek Vasut &props); 27600440bcdSDave Stevenson if (IS_ERR(bl)) { 27700440bcdSDave Stevenson ret = PTR_ERR(bl); 27800440bcdSDave Stevenson goto error; 27900440bcdSDave Stevenson } 28038573472SMarek Vasut 28138573472SMarek Vasut bl->props.brightness = 0xff; 28238573472SMarek Vasut 28338573472SMarek Vasut return 0; 28400440bcdSDave Stevenson 28500440bcdSDave Stevenson error: 28600440bcdSDave Stevenson mutex_destroy(&state->lock); 28700440bcdSDave Stevenson 28800440bcdSDave Stevenson return ret; 28900440bcdSDave Stevenson } 29000440bcdSDave Stevenson 29100440bcdSDave Stevenson static int attiny_i2c_remove(struct i2c_client *client) 29200440bcdSDave Stevenson { 29300440bcdSDave Stevenson struct attiny_lcd *state = i2c_get_clientdata(client); 29400440bcdSDave Stevenson 29500440bcdSDave Stevenson mutex_destroy(&state->lock); 29600440bcdSDave Stevenson 29700440bcdSDave Stevenson return 0; 29838573472SMarek Vasut } 29938573472SMarek Vasut 30038573472SMarek Vasut static const struct of_device_id attiny_dt_ids[] = { 30138573472SMarek Vasut { .compatible = "raspberrypi,7inch-touchscreen-panel-regulator" }, 30238573472SMarek Vasut {}, 30338573472SMarek Vasut }; 30438573472SMarek Vasut MODULE_DEVICE_TABLE(of, attiny_dt_ids); 30538573472SMarek Vasut 30638573472SMarek Vasut static struct i2c_driver attiny_regulator_driver = { 30738573472SMarek Vasut .driver = { 30838573472SMarek Vasut .name = "rpi_touchscreen_attiny", 30938573472SMarek Vasut .of_match_table = of_match_ptr(attiny_dt_ids), 31038573472SMarek Vasut }, 31138573472SMarek Vasut .probe = attiny_i2c_probe, 31200440bcdSDave Stevenson .remove = attiny_i2c_remove, 31338573472SMarek Vasut }; 31438573472SMarek Vasut 31538573472SMarek Vasut module_i2c_driver(attiny_regulator_driver); 31638573472SMarek Vasut 31738573472SMarek Vasut MODULE_AUTHOR("Marek Vasut <marex@denx.de>"); 31838573472SMarek Vasut MODULE_DESCRIPTION("Regulator device driver for Raspberry Pi 7-inch touchscreen"); 31938573472SMarek Vasut MODULE_LICENSE("GPL v2"); 320