mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
214 lines
5.0 KiB
214 lines
5.0 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Copyright (C) 2020 Marek Vasut <[email protected]> |
|
* |
|
* Based on rpi_touchscreen.c by Eric Anholt <[email protected]> |
|
*/ |
|
|
|
#include <linux/backlight.h> |
|
#include <linux/err.h> |
|
#include <linux/gpio.h> |
|
#include <linux/i2c.h> |
|
#include <linux/init.h> |
|
#include <linux/interrupt.h> |
|
#include <linux/module.h> |
|
#include <linux/regmap.h> |
|
#include <linux/regulator/driver.h> |
|
#include <linux/regulator/machine.h> |
|
#include <linux/regulator/of_regulator.h> |
|
#include <linux/slab.h> |
|
|
|
/* I2C registers of the Atmel microcontroller. */ |
|
#define REG_ID 0x80 |
|
#define REG_PORTA 0x81 |
|
#define REG_PORTA_HF BIT(2) |
|
#define REG_PORTA_VF BIT(3) |
|
#define REG_PORTB 0x82 |
|
#define REG_POWERON 0x85 |
|
#define REG_PWM 0x86 |
|
|
|
static const struct regmap_config attiny_regmap_config = { |
|
.reg_bits = 8, |
|
.val_bits = 8, |
|
.max_register = REG_PWM, |
|
.cache_type = REGCACHE_NONE, |
|
}; |
|
|
|
static int attiny_lcd_power_enable(struct regulator_dev *rdev) |
|
{ |
|
unsigned int data; |
|
|
|
regmap_write(rdev->regmap, REG_POWERON, 1); |
|
/* Wait for nPWRDWN to go low to indicate poweron is done. */ |
|
regmap_read_poll_timeout(rdev->regmap, REG_PORTB, data, |
|
data & BIT(0), 10, 1000000); |
|
|
|
/* Default to the same orientation as the closed source |
|
* firmware used for the panel. Runtime rotation |
|
* configuration will be supported using VC4's plane |
|
* orientation bits. |
|
*/ |
|
regmap_write(rdev->regmap, REG_PORTA, BIT(2)); |
|
|
|
return 0; |
|
} |
|
|
|
static int attiny_lcd_power_disable(struct regulator_dev *rdev) |
|
{ |
|
regmap_write(rdev->regmap, REG_PWM, 0); |
|
regmap_write(rdev->regmap, REG_POWERON, 0); |
|
udelay(1); |
|
return 0; |
|
} |
|
|
|
static int attiny_lcd_power_is_enabled(struct regulator_dev *rdev) |
|
{ |
|
unsigned int data; |
|
int ret; |
|
|
|
ret = regmap_read(rdev->regmap, REG_POWERON, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
if (!(data & BIT(0))) |
|
return 0; |
|
|
|
ret = regmap_read(rdev->regmap, REG_PORTB, &data); |
|
if (ret < 0) |
|
return ret; |
|
|
|
return data & BIT(0); |
|
} |
|
|
|
static const struct regulator_init_data attiny_regulator_default = { |
|
.constraints = { |
|
.valid_ops_mask = REGULATOR_CHANGE_STATUS, |
|
}, |
|
}; |
|
|
|
static const struct regulator_ops attiny_regulator_ops = { |
|
.enable = attiny_lcd_power_enable, |
|
.disable = attiny_lcd_power_disable, |
|
.is_enabled = attiny_lcd_power_is_enabled, |
|
}; |
|
|
|
static const struct regulator_desc attiny_regulator = { |
|
.name = "tc358762-power", |
|
.ops = &attiny_regulator_ops, |
|
.type = REGULATOR_VOLTAGE, |
|
.owner = THIS_MODULE, |
|
}; |
|
|
|
static int attiny_update_status(struct backlight_device *bl) |
|
{ |
|
struct regmap *regmap = bl_get_data(bl); |
|
int brightness = bl->props.brightness; |
|
|
|
if (bl->props.power != FB_BLANK_UNBLANK || |
|
bl->props.fb_blank != FB_BLANK_UNBLANK) |
|
brightness = 0; |
|
|
|
return regmap_write(regmap, REG_PWM, brightness); |
|
} |
|
|
|
static int attiny_get_brightness(struct backlight_device *bl) |
|
{ |
|
struct regmap *regmap = bl_get_data(bl); |
|
int ret, brightness; |
|
|
|
ret = regmap_read(regmap, REG_PWM, &brightness); |
|
if (ret) |
|
return ret; |
|
|
|
return brightness; |
|
} |
|
|
|
static const struct backlight_ops attiny_bl = { |
|
.update_status = attiny_update_status, |
|
.get_brightness = attiny_get_brightness, |
|
}; |
|
|
|
/* |
|
* I2C driver interface functions |
|
*/ |
|
static int attiny_i2c_probe(struct i2c_client *i2c, |
|
const struct i2c_device_id *id) |
|
{ |
|
struct backlight_properties props = { }; |
|
struct regulator_config config = { }; |
|
struct backlight_device *bl; |
|
struct regulator_dev *rdev; |
|
struct regmap *regmap; |
|
unsigned int data; |
|
int ret; |
|
|
|
regmap = devm_regmap_init_i2c(i2c, &attiny_regmap_config); |
|
if (IS_ERR(regmap)) { |
|
ret = PTR_ERR(regmap); |
|
dev_err(&i2c->dev, "Failed to allocate register map: %d\n", |
|
ret); |
|
return ret; |
|
} |
|
|
|
ret = regmap_read(regmap, REG_ID, &data); |
|
if (ret < 0) { |
|
dev_err(&i2c->dev, "Failed to read REG_ID reg: %d\n", ret); |
|
return ret; |
|
} |
|
|
|
switch (data) { |
|
case 0xde: /* ver 1 */ |
|
case 0xc3: /* ver 2 */ |
|
break; |
|
default: |
|
dev_err(&i2c->dev, "Unknown Atmel firmware revision: 0x%02x\n", data); |
|
return -ENODEV; |
|
} |
|
|
|
regmap_write(regmap, REG_POWERON, 0); |
|
mdelay(1); |
|
|
|
config.dev = &i2c->dev; |
|
config.regmap = regmap; |
|
config.of_node = i2c->dev.of_node; |
|
config.init_data = &attiny_regulator_default; |
|
|
|
rdev = devm_regulator_register(&i2c->dev, &attiny_regulator, &config); |
|
if (IS_ERR(rdev)) { |
|
dev_err(&i2c->dev, "Failed to register ATTINY regulator\n"); |
|
return PTR_ERR(rdev); |
|
} |
|
|
|
props.type = BACKLIGHT_RAW; |
|
props.max_brightness = 0xff; |
|
bl = devm_backlight_device_register(&i2c->dev, |
|
"7inch-touchscreen-panel-bl", |
|
&i2c->dev, regmap, &attiny_bl, |
|
&props); |
|
if (IS_ERR(bl)) |
|
return PTR_ERR(bl); |
|
|
|
bl->props.brightness = 0xff; |
|
|
|
return 0; |
|
} |
|
|
|
static const struct of_device_id attiny_dt_ids[] = { |
|
{ .compatible = "raspberrypi,7inch-touchscreen-panel-regulator" }, |
|
{}, |
|
}; |
|
MODULE_DEVICE_TABLE(of, attiny_dt_ids); |
|
|
|
static struct i2c_driver attiny_regulator_driver = { |
|
.driver = { |
|
.name = "rpi_touchscreen_attiny", |
|
.of_match_table = of_match_ptr(attiny_dt_ids), |
|
}, |
|
.probe = attiny_i2c_probe, |
|
}; |
|
|
|
module_i2c_driver(attiny_regulator_driver); |
|
|
|
MODULE_AUTHOR("Marek Vasut <[email protected]>"); |
|
MODULE_DESCRIPTION("Regulator device driver for Raspberry Pi 7-inch touchscreen"); |
|
MODULE_LICENSE("GPL v2");
|
|
|