2023-05-19 13:04:03 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
/*
|
2023-11-25 20:05:17 +00:00
|
|
|
* Awinic AW20036/AW20054/AW20072/AW20108 LED driver
|
2023-05-19 13:04:03 +00:00
|
|
|
*
|
|
|
|
* Copyright (c) 2023, SberDevices. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Author: Martin Kurbanov <mmkurbanov@sberdevices.ru>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/bitfield.h>
|
|
|
|
#include <linux/bits.h>
|
|
|
|
#include <linux/container_of.h>
|
2023-11-25 20:05:10 +00:00
|
|
|
#include <linux/gpio/consumer.h>
|
2023-05-19 13:04:03 +00:00
|
|
|
#include <linux/i2c.h>
|
|
|
|
#include <linux/leds.h>
|
|
|
|
#include <linux/mod_devicetable.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/mutex.h>
|
|
|
|
#include <linux/regmap.h>
|
|
|
|
#include <linux/time.h>
|
|
|
|
#include <linux/units.h>
|
|
|
|
|
|
|
|
#define AW200XX_DIM_MAX (BIT(6) - 1)
|
|
|
|
#define AW200XX_FADE_MAX (BIT(8) - 1)
|
|
|
|
#define AW200XX_IMAX_DEFAULT_uA 60000
|
|
|
|
#define AW200XX_IMAX_MAX_uA 160000
|
|
|
|
#define AW200XX_IMAX_MIN_uA 3300
|
|
|
|
|
|
|
|
/* Page 0 */
|
|
|
|
#define AW200XX_REG_PAGE0_BASE 0xc000
|
|
|
|
|
|
|
|
/* Select page register */
|
|
|
|
#define AW200XX_REG_PAGE 0xF0
|
|
|
|
#define AW200XX_PAGE_MASK (GENMASK(7, 6) | GENMASK(2, 0))
|
|
|
|
#define AW200XX_PAGE_SHIFT 0
|
|
|
|
#define AW200XX_NUM_PAGES 6
|
|
|
|
#define AW200XX_PAGE_SIZE 256
|
|
|
|
#define AW200XX_REG(page, reg) \
|
|
|
|
(AW200XX_REG_PAGE0_BASE + (page) * AW200XX_PAGE_SIZE + (reg))
|
|
|
|
#define AW200XX_REG_MAX \
|
|
|
|
AW200XX_REG(AW200XX_NUM_PAGES - 1, AW200XX_PAGE_SIZE - 1)
|
|
|
|
#define AW200XX_PAGE0 0
|
|
|
|
#define AW200XX_PAGE1 1
|
|
|
|
#define AW200XX_PAGE2 2
|
|
|
|
#define AW200XX_PAGE3 3
|
|
|
|
#define AW200XX_PAGE4 4
|
|
|
|
#define AW200XX_PAGE5 5
|
|
|
|
|
|
|
|
/* Chip ID register */
|
|
|
|
#define AW200XX_REG_IDR AW200XX_REG(AW200XX_PAGE0, 0x00)
|
|
|
|
#define AW200XX_IDR_CHIPID 0x18
|
|
|
|
|
|
|
|
/* Sleep mode register */
|
|
|
|
#define AW200XX_REG_SLPCR AW200XX_REG(AW200XX_PAGE0, 0x01)
|
|
|
|
#define AW200XX_SLPCR_ACTIVE 0x00
|
|
|
|
|
|
|
|
/* Reset register */
|
|
|
|
#define AW200XX_REG_RSTR AW200XX_REG(AW200XX_PAGE0, 0x02)
|
|
|
|
#define AW200XX_RSTR_RESET 0x01
|
|
|
|
|
|
|
|
/* Global current configuration register */
|
|
|
|
#define AW200XX_REG_GCCR AW200XX_REG(AW200XX_PAGE0, 0x03)
|
|
|
|
#define AW200XX_GCCR_IMAX_MASK GENMASK(7, 4)
|
|
|
|
#define AW200XX_GCCR_IMAX(x) ((x) << 4)
|
|
|
|
#define AW200XX_GCCR_ALLON BIT(3)
|
|
|
|
|
|
|
|
/* Fast clear display control register */
|
|
|
|
#define AW200XX_REG_FCD AW200XX_REG(AW200XX_PAGE0, 0x04)
|
|
|
|
#define AW200XX_FCD_CLEAR 0x01
|
|
|
|
|
|
|
|
/* Display size configuration */
|
|
|
|
#define AW200XX_REG_DSIZE AW200XX_REG(AW200XX_PAGE0, 0x80)
|
|
|
|
#define AW200XX_DSIZE_COLUMNS_MAX 12
|
|
|
|
|
|
|
|
#define AW200XX_LED2REG(x, columns) \
|
|
|
|
((x) + (((x) / (columns)) * (AW200XX_DSIZE_COLUMNS_MAX - (columns))))
|
|
|
|
|
2023-11-25 20:05:09 +00:00
|
|
|
/* DIM current configuration register on page 1 */
|
|
|
|
#define AW200XX_REG_DIM_PAGE1(x, columns) \
|
|
|
|
AW200XX_REG(AW200XX_PAGE1, AW200XX_LED2REG(x, columns))
|
|
|
|
|
2023-05-19 13:04:03 +00:00
|
|
|
/*
|
|
|
|
* DIM current configuration register (page 4).
|
|
|
|
* The even address for current DIM configuration.
|
|
|
|
* The odd address for current FADE configuration
|
|
|
|
*/
|
|
|
|
#define AW200XX_REG_DIM(x, columns) \
|
|
|
|
AW200XX_REG(AW200XX_PAGE4, AW200XX_LED2REG(x, columns) * 2)
|
|
|
|
#define AW200XX_REG_DIM2FADE(x) ((x) + 1)
|
2023-11-25 20:05:16 +00:00
|
|
|
#define AW200XX_REG_FADE2DIM(fade) \
|
|
|
|
DIV_ROUND_UP((fade) * AW200XX_DIM_MAX, AW200XX_FADE_MAX)
|
2023-05-19 13:04:03 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Duty ratio of display scan (see p.15 of datasheet for formula):
|
|
|
|
* duty = (592us / 600.5us) * (1 / (display_rows + 1))
|
|
|
|
*
|
|
|
|
* Multiply to 1000 (MILLI) to improve the accuracy of calculations.
|
|
|
|
*/
|
|
|
|
#define AW200XX_DUTY_RATIO(rows) \
|
|
|
|
(((592UL * USEC_PER_SEC) / 600500UL) * (MILLI / (rows)) / MILLI)
|
|
|
|
|
|
|
|
struct aw200xx_chipdef {
|
|
|
|
u32 channels;
|
|
|
|
u32 display_size_rows_max;
|
|
|
|
u32 display_size_columns;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct aw200xx_led {
|
|
|
|
struct led_classdev cdev;
|
|
|
|
struct aw200xx *chip;
|
|
|
|
int dim;
|
|
|
|
u32 num;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct aw200xx {
|
|
|
|
const struct aw200xx_chipdef *cdef;
|
|
|
|
struct i2c_client *client;
|
|
|
|
struct regmap *regmap;
|
|
|
|
struct mutex mutex;
|
|
|
|
u32 num_leds;
|
|
|
|
u32 display_rows;
|
2023-11-25 20:05:10 +00:00
|
|
|
struct gpio_desc *hwen;
|
2023-09-15 20:09:39 +00:00
|
|
|
struct aw200xx_led leds[] __counted_by(num_leds);
|
2023-05-19 13:04:03 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static ssize_t dim_show(struct device *dev, struct device_attribute *devattr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
struct led_classdev *cdev = dev_get_drvdata(dev);
|
|
|
|
struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev);
|
|
|
|
int dim = led->dim;
|
|
|
|
|
|
|
|
if (dim < 0)
|
|
|
|
return sysfs_emit(buf, "auto\n");
|
|
|
|
|
|
|
|
return sysfs_emit(buf, "%d\n", dim);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t dim_store(struct device *dev, struct device_attribute *devattr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
struct led_classdev *cdev = dev_get_drvdata(dev);
|
|
|
|
struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev);
|
|
|
|
struct aw200xx *chip = led->chip;
|
|
|
|
u32 columns = chip->cdef->display_size_columns;
|
|
|
|
int dim;
|
|
|
|
ssize_t ret;
|
|
|
|
|
|
|
|
if (sysfs_streq(buf, "auto")) {
|
|
|
|
dim = -1;
|
|
|
|
} else {
|
|
|
|
ret = kstrtoint(buf, 0, &dim);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
if (dim > AW200XX_DIM_MAX)
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_lock(&chip->mutex);
|
|
|
|
|
|
|
|
if (dim >= 0) {
|
|
|
|
ret = regmap_write(chip->regmap,
|
2023-11-25 20:05:09 +00:00
|
|
|
AW200XX_REG_DIM_PAGE1(led->num, columns),
|
|
|
|
dim);
|
2023-05-19 13:04:03 +00:00
|
|
|
if (ret)
|
|
|
|
goto out_unlock;
|
|
|
|
}
|
|
|
|
|
|
|
|
led->dim = dim;
|
|
|
|
ret = count;
|
|
|
|
|
|
|
|
out_unlock:
|
|
|
|
mutex_unlock(&chip->mutex);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
static DEVICE_ATTR_RW(dim);
|
|
|
|
|
|
|
|
static struct attribute *dim_attrs[] = {
|
|
|
|
&dev_attr_dim.attr,
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
ATTRIBUTE_GROUPS(dim);
|
|
|
|
|
|
|
|
static int aw200xx_brightness_set(struct led_classdev *cdev,
|
|
|
|
enum led_brightness brightness)
|
|
|
|
{
|
|
|
|
struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev);
|
|
|
|
struct aw200xx *chip = led->chip;
|
|
|
|
int dim;
|
|
|
|
u32 reg;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
mutex_lock(&chip->mutex);
|
|
|
|
|
|
|
|
reg = AW200XX_REG_DIM(led->num, chip->cdef->display_size_columns);
|
|
|
|
|
|
|
|
dim = led->dim;
|
|
|
|
if (dim < 0)
|
2023-11-25 20:05:16 +00:00
|
|
|
dim = AW200XX_REG_FADE2DIM(brightness);
|
2023-05-19 13:04:03 +00:00
|
|
|
|
|
|
|
ret = regmap_write(chip->regmap, reg, dim);
|
|
|
|
if (ret)
|
|
|
|
goto out_unlock;
|
|
|
|
|
|
|
|
ret = regmap_write(chip->regmap,
|
|
|
|
AW200XX_REG_DIM2FADE(reg), brightness);
|
|
|
|
|
|
|
|
out_unlock:
|
|
|
|
mutex_unlock(&chip->mutex);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static u32 aw200xx_imax_from_global(const struct aw200xx *const chip,
|
|
|
|
u32 global_imax_uA)
|
|
|
|
{
|
|
|
|
u64 led_imax_uA;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The output current of each LED (see p.14 of datasheet for formula):
|
|
|
|
* Iled = Imax * (dim / 63) * ((fade + 1) / 256) * duty
|
|
|
|
*
|
|
|
|
* The value of duty is determined by the following formula:
|
|
|
|
* duty = (592us / 600.5us) * (1 / (display_rows + 1))
|
|
|
|
*
|
|
|
|
* Calculated for the maximum values of fade and dim.
|
|
|
|
* We divide by 1000 because we earlier multiplied by 1000 to improve
|
|
|
|
* accuracy when calculating the duty.
|
|
|
|
*/
|
|
|
|
led_imax_uA = global_imax_uA * AW200XX_DUTY_RATIO(chip->display_rows);
|
|
|
|
do_div(led_imax_uA, MILLI);
|
|
|
|
|
|
|
|
return led_imax_uA;
|
|
|
|
}
|
|
|
|
|
|
|
|
static u32 aw200xx_imax_to_global(const struct aw200xx *const chip,
|
|
|
|
u32 led_imax_uA)
|
|
|
|
{
|
|
|
|
u32 duty = AW200XX_DUTY_RATIO(chip->display_rows);
|
|
|
|
|
|
|
|
/* The output current of each LED (see p.14 of datasheet for formula) */
|
|
|
|
return (led_imax_uA * 1000U) / duty;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define AW200XX_IMAX_MULTIPLIER1 10000
|
|
|
|
#define AW200XX_IMAX_MULTIPLIER2 3333
|
|
|
|
#define AW200XX_IMAX_BASE_VAL1 0
|
|
|
|
#define AW200XX_IMAX_BASE_VAL2 8
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The AW200XX has a 4-bit register (GCCR) to configure the global current,
|
|
|
|
* which ranges from 3.3mA to 160mA. The following table indicates the values
|
|
|
|
* of the global current, divided into two parts:
|
|
|
|
*
|
|
|
|
* +-----------+-----------------+-----------+-----------------+
|
|
|
|
* | reg value | global max (mA) | reg value | global max (mA) |
|
|
|
|
* +-----------+-----------------+-----------+-----------------+
|
|
|
|
* | 0 | 10 | 8 | 3.3 |
|
|
|
|
* | 1 | 20 | 9 | 6.7 |
|
|
|
|
* | 2 | 30 | 10 | 10 |
|
|
|
|
* | 3 | 40 | 11 | 13.3 |
|
|
|
|
* | 4 | 60 | 12 | 20 |
|
|
|
|
* | 5 | 80 | 13 | 26.7 |
|
|
|
|
* | 6 | 120 | 14 | 40 |
|
|
|
|
* | 7 | 160 | 15 | 53.3 |
|
|
|
|
* +-----------+-----------------+-----------+-----------------+
|
|
|
|
*
|
|
|
|
* The left part with a multiplier of 10, and the right part with a multiplier
|
|
|
|
* of 3.3.
|
|
|
|
* So we have two formulas to calculate the global current:
|
|
|
|
* for the left part of the table:
|
|
|
|
* imax = coefficient * 10
|
|
|
|
*
|
|
|
|
* for the right part of the table:
|
|
|
|
* imax = coefficient * 3.3
|
|
|
|
*
|
|
|
|
* The coefficient table consists of the following values:
|
|
|
|
* 1, 2, 3, 4, 6, 8, 12, 16.
|
|
|
|
*/
|
|
|
|
static int aw200xx_set_imax(const struct aw200xx *const chip,
|
|
|
|
u32 led_imax_uA)
|
|
|
|
{
|
|
|
|
u32 g_imax_uA = aw200xx_imax_to_global(chip, led_imax_uA);
|
2024-01-19 23:26:30 +00:00
|
|
|
static const u32 coeff_table[] = {1, 2, 3, 4, 6, 8, 12, 16};
|
2023-05-19 13:04:03 +00:00
|
|
|
u32 gccr_imax = UINT_MAX;
|
|
|
|
u32 cur_imax = 0;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(coeff_table); i++) {
|
|
|
|
u32 imax;
|
|
|
|
|
|
|
|
/* select closest ones */
|
|
|
|
imax = coeff_table[i] * AW200XX_IMAX_MULTIPLIER1;
|
|
|
|
if (g_imax_uA >= imax && imax > cur_imax) {
|
|
|
|
cur_imax = imax;
|
|
|
|
gccr_imax = i + AW200XX_IMAX_BASE_VAL1;
|
|
|
|
}
|
|
|
|
|
|
|
|
imax = coeff_table[i] * AW200XX_IMAX_MULTIPLIER2;
|
|
|
|
imax = DIV_ROUND_CLOSEST(imax, 100) * 100;
|
|
|
|
if (g_imax_uA >= imax && imax > cur_imax) {
|
|
|
|
cur_imax = imax;
|
|
|
|
gccr_imax = i + AW200XX_IMAX_BASE_VAL2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (gccr_imax == UINT_MAX)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
return regmap_update_bits(chip->regmap, AW200XX_REG_GCCR,
|
|
|
|
AW200XX_GCCR_IMAX_MASK,
|
|
|
|
AW200XX_GCCR_IMAX(gccr_imax));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int aw200xx_chip_reset(const struct aw200xx *const chip)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = regmap_write(chip->regmap, AW200XX_REG_RSTR, AW200XX_RSTR_RESET);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2023-11-25 20:05:14 +00:00
|
|
|
/* According to the datasheet software reset takes at least 1ms */
|
|
|
|
fsleep(1000);
|
|
|
|
|
2023-05-19 13:04:03 +00:00
|
|
|
regcache_mark_dirty(chip->regmap);
|
|
|
|
return regmap_write(chip->regmap, AW200XX_REG_FCD, AW200XX_FCD_CLEAR);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int aw200xx_chip_init(const struct aw200xx *const chip)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = regmap_write(chip->regmap, AW200XX_REG_DSIZE,
|
|
|
|
chip->display_rows - 1);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = regmap_write(chip->regmap, AW200XX_REG_SLPCR,
|
|
|
|
AW200XX_SLPCR_ACTIVE);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return regmap_update_bits(chip->regmap, AW200XX_REG_GCCR,
|
|
|
|
AW200XX_GCCR_ALLON, AW200XX_GCCR_ALLON);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int aw200xx_chip_check(const struct aw200xx *const chip)
|
|
|
|
{
|
|
|
|
struct device *dev = &chip->client->dev;
|
|
|
|
u32 chipid;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = regmap_read(chip->regmap, AW200XX_REG_IDR, &chipid);
|
|
|
|
if (ret)
|
|
|
|
return dev_err_probe(dev, ret, "Failed to read chip ID\n");
|
|
|
|
|
|
|
|
if (chipid != AW200XX_IDR_CHIPID)
|
|
|
|
return dev_err_probe(dev, -ENODEV,
|
|
|
|
"Chip reported wrong ID: %x\n", chipid);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-11-25 20:05:10 +00:00
|
|
|
static void aw200xx_enable(const struct aw200xx *const chip)
|
|
|
|
{
|
|
|
|
gpiod_set_value_cansleep(chip->hwen, 1);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* After HWEN pin set high the chip begins to load the OTP information,
|
|
|
|
* which takes 200us to complete. About 200us wait time is needed for
|
|
|
|
* internal oscillator startup and display SRAM initialization. After
|
|
|
|
* display SRAM initialization, the registers in page1 to page5 can be
|
|
|
|
* configured via i2c interface.
|
|
|
|
*/
|
|
|
|
fsleep(400);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void aw200xx_disable(const struct aw200xx *const chip)
|
|
|
|
{
|
|
|
|
return gpiod_set_value_cansleep(chip->hwen, 0);
|
|
|
|
}
|
|
|
|
|
2023-11-25 20:05:12 +00:00
|
|
|
static int aw200xx_probe_get_display_rows(struct device *dev,
|
|
|
|
struct aw200xx *chip)
|
|
|
|
{
|
|
|
|
struct fwnode_handle *child;
|
|
|
|
u32 max_source = 0;
|
|
|
|
|
|
|
|
device_for_each_child_node(dev, child) {
|
|
|
|
u32 source;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = fwnode_property_read_u32(child, "reg", &source);
|
|
|
|
if (ret || source >= chip->cdef->channels)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
max_source = max(max_source, source);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (max_source == 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
chip->display_rows = max_source / chip->cdef->display_size_columns + 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-05-19 13:04:03 +00:00
|
|
|
static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip)
|
|
|
|
{
|
|
|
|
u32 current_min, current_max, min_uA;
|
|
|
|
int ret;
|
|
|
|
int i;
|
|
|
|
|
2023-11-25 20:05:12 +00:00
|
|
|
ret = aw200xx_probe_get_display_rows(dev, chip);
|
2023-05-19 13:04:03 +00:00
|
|
|
if (ret)
|
|
|
|
return dev_err_probe(dev, ret,
|
2023-11-25 20:05:12 +00:00
|
|
|
"No valid led definitions found\n");
|
2023-05-19 13:04:03 +00:00
|
|
|
|
|
|
|
current_max = aw200xx_imax_from_global(chip, AW200XX_IMAX_MAX_uA);
|
|
|
|
current_min = aw200xx_imax_from_global(chip, AW200XX_IMAX_MIN_uA);
|
|
|
|
min_uA = UINT_MAX;
|
|
|
|
i = 0;
|
|
|
|
|
2024-09-26 23:20:55 +00:00
|
|
|
device_for_each_child_node_scoped(dev, child) {
|
2023-05-19 13:04:03 +00:00
|
|
|
struct led_init_data init_data = {};
|
|
|
|
struct aw200xx_led *led;
|
|
|
|
u32 source, imax;
|
|
|
|
|
|
|
|
ret = fwnode_property_read_u32(child, "reg", &source);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "Missing reg property\n");
|
|
|
|
chip->num_leds--;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (source >= chip->cdef->channels) {
|
|
|
|
dev_err(dev, "LED reg %u out of range (max %u)\n",
|
|
|
|
source, chip->cdef->channels);
|
|
|
|
chip->num_leds--;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = fwnode_property_read_u32(child, "led-max-microamp",
|
|
|
|
&imax);
|
|
|
|
if (ret) {
|
|
|
|
dev_info(&chip->client->dev,
|
|
|
|
"DT property led-max-microamp is missing\n");
|
|
|
|
} else if (imax < current_min || imax > current_max) {
|
|
|
|
dev_err(dev, "Invalid value %u for led-max-microamp\n",
|
|
|
|
imax);
|
|
|
|
chip->num_leds--;
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
min_uA = min(min_uA, imax);
|
|
|
|
}
|
|
|
|
|
|
|
|
led = &chip->leds[i];
|
|
|
|
led->dim = -1;
|
|
|
|
led->num = source;
|
|
|
|
led->chip = chip;
|
|
|
|
led->cdev.brightness_set_blocking = aw200xx_brightness_set;
|
2023-11-25 20:05:16 +00:00
|
|
|
led->cdev.max_brightness = AW200XX_FADE_MAX;
|
2023-05-19 13:04:03 +00:00
|
|
|
led->cdev.groups = dim_groups;
|
|
|
|
init_data.fwnode = child;
|
|
|
|
|
|
|
|
ret = devm_led_classdev_register_ext(dev, &led->cdev,
|
|
|
|
&init_data);
|
2024-09-26 23:20:55 +00:00
|
|
|
if (ret)
|
2023-05-19 13:04:03 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!chip->num_leds)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (min_uA == UINT_MAX) {
|
|
|
|
min_uA = aw200xx_imax_from_global(chip,
|
|
|
|
AW200XX_IMAX_DEFAULT_uA);
|
|
|
|
}
|
|
|
|
|
|
|
|
return aw200xx_set_imax(chip, min_uA);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct regmap_range_cfg aw200xx_ranges[] = {
|
|
|
|
{
|
|
|
|
.name = "aw200xx",
|
|
|
|
.range_min = 0,
|
|
|
|
.range_max = AW200XX_REG_MAX,
|
|
|
|
.selector_reg = AW200XX_REG_PAGE,
|
|
|
|
.selector_mask = AW200XX_PAGE_MASK,
|
|
|
|
.selector_shift = AW200XX_PAGE_SHIFT,
|
|
|
|
.window_start = 0,
|
|
|
|
.window_len = AW200XX_PAGE_SIZE,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct regmap_range aw200xx_writeonly_ranges[] = {
|
|
|
|
regmap_reg_range(AW200XX_REG(AW200XX_PAGE1, 0x00), AW200XX_REG_MAX),
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct regmap_access_table aw200xx_readable_table = {
|
|
|
|
.no_ranges = aw200xx_writeonly_ranges,
|
|
|
|
.n_no_ranges = ARRAY_SIZE(aw200xx_writeonly_ranges),
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct regmap_range aw200xx_readonly_ranges[] = {
|
|
|
|
regmap_reg_range(AW200XX_REG_IDR, AW200XX_REG_IDR),
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct regmap_access_table aw200xx_writeable_table = {
|
|
|
|
.no_ranges = aw200xx_readonly_ranges,
|
|
|
|
.n_no_ranges = ARRAY_SIZE(aw200xx_readonly_ranges),
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct regmap_config aw200xx_regmap_config = {
|
|
|
|
.reg_bits = 8,
|
|
|
|
.val_bits = 8,
|
|
|
|
.max_register = AW200XX_REG_MAX,
|
|
|
|
.ranges = aw200xx_ranges,
|
|
|
|
.num_ranges = ARRAY_SIZE(aw200xx_ranges),
|
|
|
|
.rd_table = &aw200xx_readable_table,
|
|
|
|
.wr_table = &aw200xx_writeable_table,
|
2023-09-29 15:23:36 +00:00
|
|
|
.cache_type = REGCACHE_MAPLE,
|
2023-11-25 20:05:15 +00:00
|
|
|
.disable_locking = true,
|
2023-05-19 13:04:03 +00:00
|
|
|
};
|
|
|
|
|
2024-04-11 16:10:27 +00:00
|
|
|
static void aw200xx_chip_reset_action(void *data)
|
|
|
|
{
|
|
|
|
aw200xx_chip_reset(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void aw200xx_disable_action(void *data)
|
|
|
|
{
|
|
|
|
aw200xx_disable(data);
|
|
|
|
}
|
|
|
|
|
2023-05-19 13:04:03 +00:00
|
|
|
static int aw200xx_probe(struct i2c_client *client)
|
|
|
|
{
|
|
|
|
const struct aw200xx_chipdef *cdef;
|
|
|
|
struct aw200xx *chip;
|
|
|
|
int count;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
cdef = device_get_match_data(&client->dev);
|
|
|
|
if (!cdef)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
count = device_get_child_node_count(&client->dev);
|
|
|
|
if (!count || count > cdef->channels)
|
|
|
|
return dev_err_probe(&client->dev, -EINVAL,
|
|
|
|
"Incorrect number of leds (%d)", count);
|
|
|
|
|
|
|
|
chip = devm_kzalloc(&client->dev, struct_size(chip, leds, count),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!chip)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
chip->cdef = cdef;
|
|
|
|
chip->num_leds = count;
|
|
|
|
chip->client = client;
|
|
|
|
i2c_set_clientdata(client, chip);
|
|
|
|
|
|
|
|
chip->regmap = devm_regmap_init_i2c(client, &aw200xx_regmap_config);
|
|
|
|
if (IS_ERR(chip->regmap))
|
|
|
|
return PTR_ERR(chip->regmap);
|
|
|
|
|
2023-11-25 20:05:10 +00:00
|
|
|
chip->hwen = devm_gpiod_get_optional(&client->dev, "enable",
|
|
|
|
GPIOD_OUT_HIGH);
|
|
|
|
if (IS_ERR(chip->hwen))
|
|
|
|
return dev_err_probe(&client->dev, PTR_ERR(chip->hwen),
|
|
|
|
"Cannot get enable GPIO");
|
|
|
|
|
|
|
|
aw200xx_enable(chip);
|
|
|
|
|
2024-04-11 16:10:27 +00:00
|
|
|
ret = devm_add_action(&client->dev, aw200xx_disable_action, chip);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2023-05-19 13:04:03 +00:00
|
|
|
ret = aw200xx_chip_check(chip);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2024-04-11 16:10:27 +00:00
|
|
|
ret = devm_mutex_init(&client->dev, &chip->mutex);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2023-05-19 13:04:03 +00:00
|
|
|
|
|
|
|
/* Need a lock now since after call aw200xx_probe_fw, sysfs nodes created */
|
|
|
|
mutex_lock(&chip->mutex);
|
|
|
|
|
|
|
|
ret = aw200xx_chip_reset(chip);
|
|
|
|
if (ret)
|
|
|
|
goto out_unlock;
|
|
|
|
|
2024-04-11 16:10:27 +00:00
|
|
|
ret = devm_add_action(&client->dev, aw200xx_chip_reset_action, chip);
|
|
|
|
if (ret)
|
|
|
|
goto out_unlock;
|
|
|
|
|
2023-05-19 13:04:03 +00:00
|
|
|
ret = aw200xx_probe_fw(&client->dev, chip);
|
|
|
|
if (ret)
|
|
|
|
goto out_unlock;
|
|
|
|
|
|
|
|
ret = aw200xx_chip_init(chip);
|
|
|
|
|
|
|
|
out_unlock:
|
2023-11-25 20:05:10 +00:00
|
|
|
if (ret)
|
|
|
|
aw200xx_disable(chip);
|
|
|
|
|
2023-05-19 13:04:03 +00:00
|
|
|
mutex_unlock(&chip->mutex);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct aw200xx_chipdef aw20036_cdef = {
|
|
|
|
.channels = 36,
|
|
|
|
.display_size_rows_max = 3,
|
|
|
|
.display_size_columns = 12,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct aw200xx_chipdef aw20054_cdef = {
|
|
|
|
.channels = 54,
|
|
|
|
.display_size_rows_max = 6,
|
|
|
|
.display_size_columns = 9,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct aw200xx_chipdef aw20072_cdef = {
|
|
|
|
.channels = 72,
|
|
|
|
.display_size_rows_max = 6,
|
|
|
|
.display_size_columns = 12,
|
|
|
|
};
|
|
|
|
|
2023-11-25 20:05:17 +00:00
|
|
|
static const struct aw200xx_chipdef aw20108_cdef = {
|
|
|
|
.channels = 108,
|
|
|
|
.display_size_rows_max = 9,
|
|
|
|
.display_size_columns = 12,
|
|
|
|
};
|
|
|
|
|
2023-05-19 13:04:03 +00:00
|
|
|
static const struct i2c_device_id aw200xx_id[] = {
|
|
|
|
{ "aw20036" },
|
|
|
|
{ "aw20054" },
|
|
|
|
{ "aw20072" },
|
2023-11-25 20:05:17 +00:00
|
|
|
{ "aw20108" },
|
2023-05-19 13:04:03 +00:00
|
|
|
{}
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(i2c, aw200xx_id);
|
|
|
|
|
|
|
|
static const struct of_device_id aw200xx_match_table[] = {
|
|
|
|
{ .compatible = "awinic,aw20036", .data = &aw20036_cdef, },
|
|
|
|
{ .compatible = "awinic,aw20054", .data = &aw20054_cdef, },
|
|
|
|
{ .compatible = "awinic,aw20072", .data = &aw20072_cdef, },
|
2023-11-25 20:05:17 +00:00
|
|
|
{ .compatible = "awinic,aw20108", .data = &aw20108_cdef, },
|
2023-05-19 13:04:03 +00:00
|
|
|
{}
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, aw200xx_match_table);
|
|
|
|
|
|
|
|
static struct i2c_driver aw200xx_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = "aw200xx",
|
|
|
|
.of_match_table = aw200xx_match_table,
|
|
|
|
},
|
2023-06-26 09:02:54 +00:00
|
|
|
.probe = aw200xx_probe,
|
2023-05-19 13:04:03 +00:00
|
|
|
.id_table = aw200xx_id,
|
|
|
|
};
|
|
|
|
module_i2c_driver(aw200xx_driver);
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Martin Kurbanov <mmkurbanov@sberdevices.ru>");
|
|
|
|
MODULE_DESCRIPTION("AW200XX LED driver");
|
|
|
|
MODULE_LICENSE("GPL");
|