diff options
| author | Dmitry Rokosov <rockosov@rulkc.org> | 2026-08-14 01:40:02 +0300 |
|---|---|---|
| committer | Dmitry Rokosov <rockosov@rulkc.org> | 2026-08-14 01:40:02 +0300 |
| commit | b1a9f82c8ec64a6c7a648b6a4ab481f1d3b9f1e4 (patch) | |
| tree | 8f0964c1f46b9cf430b5fdaa2d12bcbcbe16ada5 /drivers/leds/leds-aw200xx.c | |
| parent | 028ef9c96e96197026887c0f092424679298aae8 (diff) | |
| parent | 0e0488eb915954f96e9ce4b40c0a9d8da6eebc19 (diff) | |
| download | linux-b1a9f82c8ec64a6c7a648b6a4ab481f1d3b9f1e4.tar.gz linux-b1a9f82c8ec64a6c7a648b6a4ab481f1d3b9f1e4.zip | |
Merge tag 'linux-amlogic/landau-v7.0' into landau-next
Amlogic SoC updates for LANDAU Linux v7.0
This merge brings support for new A1 CPU/audio clocks, Amlogic BBT parser,
AW200xx LED enhancements, and new board defconfigs.
Signed-off-by: Dmitry Rokosov <rockosov@rulkc.org>
Diffstat (limited to 'drivers/leds/leds-aw200xx.c')
| -rw-r--r-- | drivers/leds/leds-aw200xx.c | 1392 |
1 files changed, 1323 insertions, 69 deletions
diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c index fe223d363a5d4..6cf983f233c71 100644 --- a/drivers/leds/leds-aw200xx.c +++ b/drivers/leds/leds-aw200xx.c @@ -20,6 +20,11 @@ #include <linux/time.h> #include <linux/units.h> +#include <linux/backlight.h> +#include <linux/fb.h> + +#define AW200XX_LEDS_MAX 108 +#define AW200XX_PATTERN_MAX 3 #define AW200XX_DIM_MAX (BIT(6) - 1) #define AW200XX_FADE_MAX (BIT(8) - 1) #define AW200XX_IMAX_DEFAULT_uA 60000 @@ -68,6 +73,88 @@ #define AW200XX_REG_FCD AW200XX_REG(AW200XX_PAGE0, 0x04) #define AW200XX_FCD_CLEAR 0x01 +/* Interrupt status register */ +#define AW200XX_REG_ISRFLT AW200XX_REG(AW200XX_PAGE0, 0x0B) +#define AW200XX_ISRFLT_PATIS_MASK GENMASK(6, 4) + +/* Pattern enable control register */ +#define AW200XX_REG_PATCR AW200XX_REG(AW200XX_PAGE0, 0x43) +#define AW200XX_PATCR_PAT_IE_MASK GENMASK(6, 4) +#define AW200XX_PATCR_PAT_IE_ALL AW200XX_PATCR_PAT_IE_MASK +#define AW200XX_PATCR_PAT_ENABLE(x) BIT(x) + +/* + * Maximum breathing level registers + * For patterns 0 - 0x44, 1 - 0x45, 2 - 0x46 (step 1) + */ +#define AW200XX_REG_PAT0_MAX_BREATH AW200XX_REG(AW200XX_PAGE0, 0x44) + +/* + * Minimum breathing level registers + * For patterns 0 - 0x47, 1 - 0x48, 2 - 0x49 (step 1) + */ +#define AW200XX_REG_PAT0_MIN_BREATH AW200XX_REG(AW200XX_PAGE0, 0x47) + +/* + * Template 1 (rise-time) & template 2 (on-time) configuration register + * For patterns 0 - 0x4A, 1 - 0x4E, 2 - 0x52 (step 4) + */ +#define AW200XX_REG_PAT0_T0 AW200XX_REG(AW200XX_PAGE0, 0x4A) + +/* + * Template 3 (fall-time) & template 4 (off-time) configuration register + * For patterns 0 - 0x4B, 1 - 0x4F, 2 - 0x53 (step 4) + */ +#define AW200XX_REG_PAT0_T1 AW200XX_REG(AW200XX_PAGE0, 0x4B) + +/* + * Loop configuration registers: + * loop end point setting (LE) + * loop beginning point setting (LB) + * MSB of loop times (LT) + * For patterns 0 - 0x4C, 1 - 0x50, 2 - 0x54 (step 4) + */ +#define AW200XX_REG_PAT0_T2 AW200XX_REG(AW200XX_PAGE0, 0x4C) +#define AW200XX_REG_PATX_T2(x) (AW200XX_REG_PAT0_T2 + (x)) + +/* + * Loop configuration registers: + * LSB of loop times (LT) + * For patterns 0 - 0x4D, 1 - 0x51, 2 - 0x55 (step 4) + */ +#define AW200XX_REG_PAT0_T3 AW200XX_REG(AW200XX_PAGE0, 0x4D) +#define AW200XX_REG_PATX_T3(x) (AW200XX_REG_PAT0_T3 + (x)) + +#define AW200XX_PAT_T2_LE_MASK GENMASK(7, 6) +#define AW200XX_PAT_T2_LB_MASK GENMASK(5, 4) +#define AW200XX_PAT_T2_LT_MASK GENMASK(3, 0) +#define AW200XX_PAT_T3_LT_MASK GENMASK(7, 0) +#define AW200XX_PAT0_T2_LT_MSB(x) ((x) >> 8) +#define AW200XX_PAT0_T3_LT_LSB(x) ((x) & 0xFF) +#define AW200XX_PAT0_T_LT(msb, lsb) ((msb) << 8 | (lsb)) +#define AW200XX_PAT0_T_LT_MAX (BIT(12) - 1) + +#define AW200XX_PAT_T_STEP 4 + +#define AW200XX_PAT_T1_T3_MASK GENMASK(7, 4) +#define AW200XX_PAT_T2_T4_MASK GENMASK(3, 0) +#define AW200XX_TEMPLATE_TIME_MAX (BIT(4) - 1) + +/* + * Pattern mode configuration register + * For patterns 0 - 0x56, 1 - 0x57, 2 - 0x58 (step 1) + */ +#define AW200XX_REG_PAT0_CFG AW200XX_REG(AW200XX_PAGE0, 0x56) +#define AW200XX_PAT_CFG_MODE_MASK BIT(0) +#define AW200XX_PAT_CFG_RAMP_MASK BIT(1) +#define AW200XX_PAT_CFG_SWITCH_MASK BIT(2) + +/* Start pattern register */ +#define AW200XX_REG_PATGO AW200XX_REG(AW200XX_PAGE0, 0x59) +#define AW200XX_PATGO(x) BIT(x) +#define AW200XX_PATGO_RUN(x, run) ((run) << (x)) +#define AW200XX_PATGO_STATE(x) BIT((x) + 4) + /* Display size configuration */ #define AW200XX_REG_DSIZE AW200XX_REG(AW200XX_PAGE0, 0x80) #define AW200XX_DSIZE_COLUMNS_MAX 12 @@ -79,6 +166,11 @@ #define AW200XX_REG_DIM_PAGE1(x, columns) \ AW200XX_REG(AW200XX_PAGE1, AW200XX_LED2REG(x, columns)) +/* Pattern selection register*/ +#define AW200XX_REG_PAT_SELECT(x, columns) \ + AW200XX_REG(AW200XX_PAGE3, AW200XX_LED2REG(x, columns)) +#define AW200XX_PATX_SELECT(x) ((x) + 1) + /* * DIM current configuration register (page 4). * The even address for current DIM configuration. @@ -89,6 +181,8 @@ #define AW200XX_REG_DIM2FADE(x) ((x) + 1) #define AW200XX_REG_FADE2DIM(fade) \ DIV_ROUND_UP((fade) * AW200XX_DIM_MAX, AW200XX_FADE_MAX) +#define AW200XX_SCALE_FADE(fade, scale) \ + DIV_ROUND_UP((fade) * (scale), AW200XX_FADE_MAX) /* * Duty ratio of display scan (see p.15 of datasheet for formula): @@ -105,6 +199,31 @@ struct aw200xx_chipdef { u32 display_size_columns; }; +struct aw200xx_page { + u8 dirty_start; + u8 dirty_end; + u8 *buf; + u32 page; +}; + +#define FB_BUFFER_COUNT 2 + +struct aw200xx_fb { + int width; + int height; + int num_leds; + u8 *map; + atomic_t open_count; + struct fb_info *info; + u32 refresh_rate; + u8 *buffer; + u8 min_reg; + u8 max_reg; + int offset_y; + struct backlight_device *bl; + u8 fade_scale; +}; + struct aw200xx_led { struct led_classdev cdev; struct aw200xx *chip; @@ -117,12 +236,539 @@ struct aw200xx { struct i2c_client *client; struct regmap *regmap; struct mutex mutex; + DECLARE_BITMAP(pattern_leds[AW200XX_PATTERN_MAX], AW200XX_LEDS_MAX); u32 num_leds; + u32 num_pattern_leds; u32 display_rows; + struct aw200xx_page dim_page; + struct aw200xx_page fade_page; + struct delayed_work drawer; + bool queue_mode; + struct aw200xx_fb fb; + u32 imax; struct gpio_desc *hwen; struct aw200xx_led leds[] __counted_by(num_leds); }; +struct aw200xx_led_data { + int num; + u32 imax; + enum led_default_state default_state; +}; + +struct aw200xx_attribute { + struct device_attribute dev_attr; + u32 reg; + u32 mask; + u32 max; +}; + +#define to_aw200xx_attr(attr) \ + container_of(attr, struct aw200xx_attribute, dev_attr) + +#define AW200XX_ATTR(_n, _m, _sh, _st, _r, _msk, _max) { \ + .dev_attr = __ATTR(_n, _m, _sh, _st), \ + .reg = _r, \ + .mask = _msk, \ + .max = _max, \ +} + +#define AW200XX_DEVICE_ATTR_RW(_v, _n, _sh, _st, _r, _msk, _max) \ +struct aw200xx_attribute _v##_attr = \ + AW200XX_ATTR(_n, 0644, _sh, _st, \ + _r, _msk, _max) + +#define AW200XX_DEVICE_ATTR_RO(_v, _n, _sh, _r, _msk, _max) \ +struct aw200xx_attribute _v##_attr = \ + AW200XX_ATTR(_n, 0444, _sh, NULL, \ + _r, _msk, _max) + +static ssize_t aw200xx_store_internal(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr); + struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev)); + u32 val; + int ret; + + ret = kstrtouint(buf, 0, &val); + if (ret < 0 || val > attr->max) + return -EINVAL; + + val <<= __ffs(attr->mask); + + mutex_lock(&chip->mutex); + ret = regmap_update_bits(chip->regmap, attr->reg, attr->mask, val); + mutex_unlock(&chip->mutex); + + if (ret) + return ret; + + return count; +} + +static ssize_t aw200xx_show_internal(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr); + struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev)); + u32 val; + int ret; + + mutex_lock(&chip->mutex); + ret = regmap_read(chip->regmap, attr->reg, &val); + mutex_unlock(&chip->mutex); + + if (ret) + return ret; + + val = (val & attr->mask) >> __ffs(attr->mask); + + return sysfs_emit(buf, "%u\n", val); +} + +static ssize_t aw200xx_template_time_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + static const u32 ttimes_ms[] = { + 0, 130, 260, 380, 510, 770, 1040, 1600, + 2100, 2600, 3100, 4200, 5200, 6200, 7300, 8300, + }; + const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr); + struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev)); + u32 ttime; + int ret; + + mutex_lock(&chip->mutex); + ret = regmap_read(chip->regmap, attr->reg, &ttime); + mutex_unlock(&chip->mutex); + + if (ret) + return ret; + + ttime = (ttime & attr->mask) >> __ffs(attr->mask); + if (ttime >= ARRAY_SIZE(ttimes_ms)) + return -EIO; + + ttime = ttimes_ms[ttime]; + + /* For On & Off time minimum is 40ms */ + if (ttime == 0 && attr->mask == AW200XX_PAT_T2_T4_MASK) + ttime = 40; + + return sysfs_emit(buf, "%ums\n", ttime); +} + +static ssize_t aw200xx_pattern_leds_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count, + bool clear) +{ + const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr); + struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev)); + unsigned long *pattern_leds = chip->pattern_leds[attr->reg]; + u32 columns = chip->cdef->display_size_columns; + DECLARE_BITMAP(leds, AW200XX_LEDS_MAX); + u32 val = clear ? 0 : AW200XX_PATX_SELECT(attr->reg); + u32 i; + ssize_t ret; + + if (sysfs_streq(buf, "all")) { + bitmap_fill(leds, chip->num_pattern_leds); + } else { + ret = bitmap_parse(buf, count, leds, chip->num_pattern_leds); + if (ret) + return -EINVAL; + } + + mutex_lock(&chip->mutex); + + if (!clear && atomic_read(&chip->fb.open_count) && + find_next_bit(leds, chip->num_pattern_leds, chip->num_leds) != + chip->num_pattern_leds) { + ret = -EBUSY; + goto out_unlock; + } + + for_each_set_bit(i, leds, chip->num_pattern_leds) { + const u32 num = i < chip->num_leds ? + chip->leds[i].num : + chip->fb.map[i - chip->num_leds]; + + ret = regmap_write(chip->regmap, + AW200XX_REG_PAT_SELECT(num, columns), val); + if (ret) + goto out_unlock; + + if (clear) + __clear_bit(i, pattern_leds); + else + __set_bit(i, pattern_leds); + } + + ret = count; + +out_unlock: + mutex_unlock(&chip->mutex); + return ret; +} + +static ssize_t aw200xx_pattern_select_leds_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr); + struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev)); + int ret; + + mutex_lock(&chip->mutex); + ret = sysfs_emit(buf, "%*pb\n", + chip->num_pattern_leds, chip->pattern_leds[attr->reg]); + mutex_unlock(&chip->mutex); + + return ret; +} + +static ssize_t aw200xx_pattern_select_leds_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + return aw200xx_pattern_leds_store(dev, devattr, buf, count, false); +} + +static ssize_t aw200xx_pattern_clear_leds_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr); + struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev)); + DECLARE_BITMAP(leds, AW200XX_LEDS_MAX); + int ret; + + mutex_lock(&chip->mutex); + bitmap_fill(leds, chip->num_pattern_leds); + bitmap_xor(leds, leds, chip->pattern_leds[attr->reg], chip->num_pattern_leds); + ret = scnprintf(buf, PAGE_SIZE, "%*pb", chip->num_pattern_leds, leds); + mutex_unlock(&chip->mutex); + + return ret; +} + +static ssize_t aw200xx_pattern_clear_leds_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + return aw200xx_pattern_leds_store(dev, devattr, buf, count, true); +} + +static ssize_t aw200xx_pattern_start_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr); + struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev)); + u32 start = 0; + u32 val; + int ret; + + mutex_lock(&chip->mutex); + + ret = regmap_read(chip->regmap, AW200XX_REG_PATCR, &val); + if (ret) + goto out_unlock; + + if (val & AW200XX_PATCR_PAT_ENABLE(attr->reg)) { + ret = regmap_read(chip->regmap, AW200XX_REG_PATGO, &val); + if (ret) + goto out_unlock; + + start = !!(val & AW200XX_PATGO(attr->reg)); + } + + ret = sysfs_emit(buf, "%u\n", start); + +out_unlock: + mutex_unlock(&chip->mutex); + return ret; +} + +static ssize_t aw200xx_pattern_start_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr); + struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev)); + u32 start; + ssize_t ret; + + ret = kstrtouint(buf, 0, &start); + if (ret < 0 || start > attr->max) + return -EINVAL; + + start = AW200XX_PATGO_RUN(attr->reg, start); + + mutex_lock(&chip->mutex); + + ret = regmap_update_bits(chip->regmap, AW200XX_REG_PATCR, + AW200XX_PATCR_PAT_ENABLE(attr->reg), start); + if (ret) + goto out_unlock; + + ret = regmap_update_bits(chip->regmap, AW200XX_REG_PATGO, + AW200XX_PATGO(attr->reg), start); + if (ret) + goto out_unlock; + + ret = count; + +out_unlock: + mutex_unlock(&chip->mutex); + return ret; +} + +static ssize_t aw200xx_pattern_running_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr); + struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev)); + u32 running; + int ret; + + mutex_lock(&chip->mutex); + + ret = regmap_read(chip->regmap, AW200XX_REG_PATGO, &running); + if (ret) + goto out_unlock; + + running &= AW200XX_PATGO_STATE(attr->reg); + ret = sysfs_emit(buf, "%u\n", !!running); + +out_unlock: + mutex_unlock(&chip->mutex); + return ret; +} + +static ssize_t aw200xx_pattern_repeat_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr); + struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev)); + u32 repeat_msb; + u32 repeat_lsb; + int ret; + + mutex_lock(&chip->mutex); + + ret = regmap_read(chip->regmap, + AW200XX_REG_PATX_T2(attr->reg), &repeat_msb); + if (ret) + goto out_unlock; + + ret = regmap_read(chip->regmap, + AW200XX_REG_PATX_T3(attr->reg), &repeat_lsb); + if (ret) + goto out_unlock; + + repeat_msb &= AW200XX_PAT_T2_LT_MASK; + repeat_lsb &= AW200XX_PAT_T3_LT_MASK; + + ret = sysfs_emit(buf, "%u\n", + AW200XX_PAT0_T_LT(repeat_msb, repeat_lsb)); + +out_unlock: + mutex_unlock(&chip->mutex); + return ret; +} + +static ssize_t aw200xx_pattern_repeat_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + const struct aw200xx_attribute *attr = to_aw200xx_attr(devattr); + struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev)); + u32 repeat; + ssize_t ret; + + ret = kstrtouint(buf, 0, &repeat); + if (ret < 0 || repeat > attr->max) + return -EINVAL; + + mutex_lock(&chip->mutex); + + ret = regmap_update_bits(chip->regmap, + AW200XX_REG_PATX_T2(attr->reg), + AW200XX_PAT_T2_LT_MASK, + AW200XX_PAT0_T2_LT_MSB(repeat)); + if (ret) + goto out_unlock; + + ret = regmap_update_bits(chip->regmap, + AW200XX_REG_PATX_T3(attr->reg), + AW200XX_PAT_T3_LT_MASK, + AW200XX_PAT0_T3_LT_LSB(repeat)); + if (ret) + goto out_unlock; + + ret = count; + +out_unlock: + mutex_unlock(&chip->mutex); + return ret; +} + +#define AW200XX_DEVICE_ATTR_PAT_RW(_n, _sh, _st, _r, _step, _msk, _max) \ +static AW200XX_DEVICE_ATTR_RW(_n##0, _n, _sh, _st, \ + _r, _msk, _max); \ +static AW200XX_DEVICE_ATTR_RW(_n##1, _n, _sh, _st, \ + _r + (1 * (_step)), _msk, _max); \ +static AW200XX_DEVICE_ATTR_RW(_n##2, _n, _sh, _st, \ + _r + (2 * (_step)), _msk, _max) + +#define AW200XX_DEVICE_ATTR_PAT_RO(_n, _sh, _r, _step, _msk, _max) \ +static AW200XX_DEVICE_ATTR_RO(_n##0, _n, _sh, \ + _r, _msk, _max); \ +static AW200XX_DEVICE_ATTR_RO(_n##1, _n, _sh, \ + _r + (1 * (_step)), _msk, _max); \ +static AW200XX_DEVICE_ATTR_RO(_n##2, _n, _sh, \ + _r + (2 * (_step)), _msk, _max) + +#define AW200XX_DEFINE_ATTR_GROUP(_idx, _a0, _a1, _a2, _a3, _a4, _a5, \ + _a6, _a7, _a8, _a9, _a10, _a11, _a12, _a13, _a14, _a15) \ +static struct attribute *aw200xx_pattern##_idx##_attributes[] = { \ + &_a0##_idx##_attr.dev_attr.attr, \ + &_a1##_idx##_attr.dev_attr.attr, \ + &_a2##_idx##_attr.dev_attr.attr, \ + &_a3##_idx##_attr.dev_attr.attr, \ + &_a4##_idx##_attr.dev_attr.attr, \ + &_a5##_idx##_attr.dev_attr.attr, \ + &_a6##_idx##_attr.dev_attr.attr, \ + &_a7##_idx##_attr.dev_attr.attr, \ + &_a8##_idx##_attr.dev_attr.attr, \ + &_a9##_idx##_attr.dev_attr.attr, \ + &_a10##_idx##_attr.dev_attr.attr, \ + &_a11##_idx##_attr.dev_attr.attr, \ + &_a12##_idx##_attr.dev_attr.attr, \ + &_a13##_idx##_attr.dev_attr.attr, \ + &_a14##_idx##_attr.dev_attr.attr, \ + &_a15##_idx##_attr.dev_attr.attr, \ + NULL}; \ +static const struct attribute_group aw200xx_pattern##_idx##_group = { \ + .attrs = aw200xx_pattern##_idx##_attributes, \ + .name = "pattern"#_idx, \ +} + +#define AW200XX_DEFINE_ATTR_GROUPS(...) \ +AW200XX_DEFINE_ATTR_GROUP(0, __VA_ARGS__); \ +AW200XX_DEFINE_ATTR_GROUP(1, __VA_ARGS__); \ +AW200XX_DEFINE_ATTR_GROUP(2, __VA_ARGS__); \ +static const struct attribute_group *aw200xx_pattern_groups[] = { \ + &aw200xx_pattern0_group, \ + &aw200xx_pattern1_group, \ + &aw200xx_pattern2_group, \ + NULL} + +AW200XX_DEVICE_ATTR_PAT_RW(rise_time, + aw200xx_template_time_show, aw200xx_store_internal, + AW200XX_REG_PAT0_T0, AW200XX_PAT_T_STEP, + AW200XX_PAT_T1_T3_MASK, AW200XX_TEMPLATE_TIME_MAX); + +AW200XX_DEVICE_ATTR_PAT_RW(on_time, + aw200xx_template_time_show, aw200xx_store_internal, + AW200XX_REG_PAT0_T0, AW200XX_PAT_T_STEP, + AW200XX_PAT_T2_T4_MASK, AW200XX_TEMPLATE_TIME_MAX); + +AW200XX_DEVICE_ATTR_PAT_RW(fall_time, + aw200xx_template_time_show, aw200xx_store_internal, + AW200XX_REG_PAT0_T1, AW200XX_PAT_T_STEP, + AW200XX_PAT_T1_T3_MASK, AW200XX_TEMPLATE_TIME_MAX); + +AW200XX_DEVICE_ATTR_PAT_RW(off_time, + aw200xx_template_time_show, aw200xx_store_internal, + AW200XX_REG_PAT0_T1, AW200XX_PAT_T_STEP, + AW200XX_PAT_T2_T4_MASK, AW200XX_TEMPLATE_TIME_MAX); + +AW200XX_DEVICE_ATTR_PAT_RW(mode, + aw200xx_show_internal, aw200xx_store_internal, + AW200XX_REG_PAT0_CFG, 1, + AW200XX_PAT_CFG_MODE_MASK, 1); + +AW200XX_DEVICE_ATTR_PAT_RW(ramp, + aw200xx_show_internal, aw200xx_store_internal, + AW200XX_REG_PAT0_CFG, 1, + AW200XX_PAT_CFG_RAMP_MASK, 1); + +AW200XX_DEVICE_ATTR_PAT_RW(toggle, + aw200xx_show_internal, aw200xx_store_internal, + AW200XX_REG_PAT0_CFG, 1, + AW200XX_PAT_CFG_SWITCH_MASK, 1); + +AW200XX_DEVICE_ATTR_PAT_RW(loop_end_on, + aw200xx_show_internal, aw200xx_store_internal, + AW200XX_REG_PAT0_T2, AW200XX_PAT_T_STEP, + AW200XX_PAT_T2_LE_MASK, 1); + +AW200XX_DEVICE_ATTR_PAT_RW(loop_begin, + aw200xx_show_internal, aw200xx_store_internal, + AW200XX_REG_PAT0_T2, AW200XX_PAT_T_STEP, + AW200XX_PAT_T2_LB_MASK, 3); + +AW200XX_DEVICE_ATTR_PAT_RW(max_breathing_level, + aw200xx_show_internal, aw200xx_store_internal, + AW200XX_REG_PAT0_MAX_BREATH, 1, + 0xFF, AW200XX_FADE_MAX); + +AW200XX_DEVICE_ATTR_PAT_RW(min_breathing_level, + aw200xx_show_internal, aw200xx_store_internal, + AW200XX_REG_PAT0_MIN_BREATH, 1, + 0xFF, AW200XX_FADE_MAX); + +AW200XX_DEVICE_ATTR_PAT_RW(start, + aw200xx_pattern_start_show, + aw200xx_pattern_start_store, + 0, 1, 1, 1); + +AW200XX_DEVICE_ATTR_PAT_RO(running, + aw200xx_pattern_running_show, 0, 1, 0, 0); + +AW200XX_DEVICE_ATTR_PAT_RW(repeat, + aw200xx_pattern_repeat_show, + aw200xx_pattern_repeat_store, + 0, AW200XX_PAT_T_STEP, + 0, AW200XX_PAT0_T_LT_MAX); + +AW200XX_DEVICE_ATTR_PAT_RW(select_leds, + aw200xx_pattern_select_leds_show, + aw200xx_pattern_select_leds_store, + 0, 1, 0, 0); + +AW200XX_DEVICE_ATTR_PAT_RW(clear_leds, + aw200xx_pattern_clear_leds_show, + aw200xx_pattern_clear_leds_store, + 0, 1, 0, 0); + +AW200XX_DEFINE_ATTR_GROUPS(start, running, mode, ramp, toggle, repeat, + loop_end_on, loop_begin, select_leds, clear_leds, + max_breathing_level, min_breathing_level, + rise_time, on_time, fall_time, off_time); + +#define IS_PAGE_DIRTY(page) ((page)->dirty_end) + +static void add_dirty_range(struct aw200xx_page *page, u8 start, u8 end) +{ + if (IS_PAGE_DIRTY(page)) { + page->dirty_start = min(page->dirty_start, start); + page->dirty_end = max(page->dirty_end, end); + } else { + page->dirty_start = start; + page->dirty_end = end; + } +} + static ssize_t dim_show(struct device *dev, struct device_attribute *devattr, char *buf) { @@ -136,7 +782,23 @@ static ssize_t dim_show(struct device *dev, struct device_attribute *devattr, return sysfs_emit(buf, "%d\n", dim); } -static ssize_t dim_store(struct device *dev, struct device_attribute *devattr, +static int aw200xx_dim_fill(const char *buf, int *dim) +{ + if (sysfs_streq(buf, "auto")) { + *dim = -1; + } else { + int ret = kstrtoint(buf, 0, dim); + if (ret) + return ret; + + if (*dim > AW200XX_DIM_MAX) + return -EINVAL; + } + + return 0; +} + +static ssize_t dim_store_queue(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct led_classdev *cdev = dev_get_drvdata(dev); @@ -146,17 +808,43 @@ static ssize_t dim_store(struct device *dev, struct device_attribute *devattr, int dim; ssize_t ret; - if (sysfs_streq(buf, "auto")) { - dim = -1; - } else { - ret = kstrtoint(buf, 0, &dim); - if (ret) - return ret; + ret = aw200xx_dim_fill(buf, &dim); + if (ret < 0) + return ret; - if (dim > AW200XX_DIM_MAX) - return -EINVAL; + mutex_lock(&chip->mutex); + + if (dim >= 0) { + const u8 reg = AW200XX_LED2REG(led->num, columns); + + chip->dim_page.buf[reg] = dim; + add_dirty_range(&chip->dim_page, reg, reg + 1); } + led->dim = dim; + + mutex_unlock(&chip->mutex); + + if (dim >= 0) + schedule_delayed_work(&chip->drawer, 0); + + return count; +} + +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; + + ret = aw200xx_dim_fill(buf, &dim); + if (ret < 0) + return ret; + mutex_lock(&chip->mutex); if (dim >= 0) { @@ -182,6 +870,71 @@ static struct attribute *dim_attrs[] = { }; ATTRIBUTE_GROUPS(dim); +static inline void aw200xx_fb_queue(struct aw200xx *chip) +{ + const struct aw200xx_fb *fbdev = &chip->fb; + + schedule_delayed_work(&chip->drawer, + msecs_to_jiffies(HZ / fbdev->refresh_rate)); +} + +static inline void aw200xx_fb_add_screen_dirty_range(struct aw200xx *chip) +{ + const struct aw200xx_fb *fbdev = &chip->fb; + const uint8_t *fb_ptr = fbdev->buffer + fbdev->offset_y * fbdev->width; + int i; + + for (i = 0; i < fbdev->num_leds; i++) { + const u8 reg = fbdev->map[i]; + const u8 fade = AW200XX_SCALE_FADE(fb_ptr[i], + fbdev->fade_scale); + + chip->fade_page.buf[reg] = fade; + chip->dim_page.buf[reg] = AW200XX_REG_FADE2DIM(fade); + } + + add_dirty_range(&chip->fade_page, fbdev->min_reg, fbdev->max_reg + 1); + add_dirty_range(&chip->dim_page, fbdev->min_reg, fbdev->max_reg + 1); +} + +static int aw200xx_update_dirty_page(struct aw200xx *chip, + struct aw200xx_page *page) +{ + int ret; + + if (!IS_PAGE_DIRTY(page)) + return 0; + + ret = regmap_raw_write(chip->regmap, page->page + page->dirty_start, + page->buf + page->dirty_start, + page->dirty_end - page->dirty_start); + if (ret) + return ret; + + page->dirty_start = 0; + page->dirty_end = 0; + + return 0; +} + +static void aw200xx_update(struct work_struct *work) +{ + struct aw200xx *chip = container_of(work, struct aw200xx, drawer.work); + + mutex_lock(&chip->mutex); + + if (atomic_read(&chip->fb.open_count)) + aw200xx_fb_add_screen_dirty_range(chip); + + aw200xx_update_dirty_page(chip, &chip->dim_page); + aw200xx_update_dirty_page(chip, &chip->fade_page); + + if (atomic_read(&chip->fb.open_count)) + aw200xx_fb_queue(chip); + + mutex_unlock(&chip->mutex); +} + static int aw200xx_brightness_set(struct led_classdev *cdev, enum led_brightness brightness) { @@ -212,6 +965,87 @@ out_unlock: return ret; } +static int aw200xx_brightness_set_queue(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev); + struct aw200xx *chip = led->chip; + u32 reg = AW200XX_LED2REG(led->num, chip->cdef->display_size_columns); + + mutex_lock(&chip->mutex); + + if (led->dim < 0) { + int dim = AW200XX_REG_FADE2DIM(brightness); + chip->dim_page.buf[reg] = dim; + add_dirty_range(&chip->dim_page, reg, reg + 1); + } + + chip->fade_page.buf[reg] = brightness; + add_dirty_range(&chip->fade_page, reg, reg + 1); + + mutex_unlock(&chip->mutex); + + schedule_delayed_work(&chip->drawer, 0); + + return 0; +} + +static irqreturn_t aw200xx_irq_thread(int irq, void *dev_id) +{ + struct aw200xx *chip = dev_id; + unsigned long pattern_state; + u32 interrupt_state; + int i; + int ret; + + mutex_lock(&chip->mutex); + ret = regmap_read(chip->regmap, AW200XX_REG_ISRFLT, &interrupt_state); + mutex_unlock(&chip->mutex); + + if (ret) { + dev_err(&chip->client->dev, + "Failed to get interrupt status: %d\n", ret); + return IRQ_HANDLED; + } + + pattern_state = FIELD_GET(AW200XX_ISRFLT_PATIS_MASK, interrupt_state); + + for_each_set_bit(i, &pattern_state, AW200XX_PATTERN_MAX) { + char dir[sizeof("patternx")]; + + snprintf(dir, sizeof(dir), "pattern%d", i); + sysfs_notify(&chip->client->dev.kobj, dir, "running"); + } + + return IRQ_HANDLED; +} + +static int aw200xx_setup_interrupts(struct aw200xx *chip) +{ + struct i2c_client *i2c = chip->client; + int ret; + + if (i2c->irq <= 0) + return 0; + + ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, + NULL, + aw200xx_irq_thread, + IRQF_ONESHOT, + i2c->name, + chip); + if (ret) + return dev_err_probe(&i2c->dev, ret, "Failed to request irq\n"); + + ret = regmap_update_bits(chip->regmap, AW200XX_REG_PATCR, + AW200XX_PATCR_PAT_IE_MASK, + AW200XX_PATCR_PAT_IE_ALL); + if (ret) + dev_err_probe(&i2c->dev, ret, "Failed to enable interrupts\n"); + + return ret; +} + static u32 aw200xx_imax_from_global(const struct aw200xx *const chip, u32 global_imax_uA) { @@ -332,6 +1166,10 @@ static int aw200xx_chip_init(const struct aw200xx *const chip) { int ret; + ret = aw200xx_set_imax(chip, chip->imax); + if (ret) + return ret; + ret = regmap_write(chip->regmap, AW200XX_REG_DSIZE, chip->display_rows - 1); if (ret) @@ -382,51 +1220,27 @@ static void aw200xx_disable(const struct aw200xx *const chip) gpiod_set_value_cansleep(chip->hwen, 0); } -static int aw200xx_probe_get_display_rows(struct device *dev, - struct aw200xx *chip) +static int aw200xx_get_display_rows(struct aw200xx *chip, int max_reg) { - 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; + return max_reg / chip->cdef->display_size_columns + 1; } -static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip) +static void aw200xx_probe_dt_leds(struct device *dev, struct aw200xx *chip, + struct aw200xx_led_data *leds, bool *keep_state) { u32 current_min, current_max, min_uA; + int count = 0; + int max_source = -1; int ret; int i; - ret = aw200xx_probe_get_display_rows(dev, chip); - if (ret) - return dev_err_probe(dev, ret, - "No valid led definitions found\n"); - - 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; device_for_each_child_node_scoped(dev, child) { - struct led_init_data init_data = {}; - struct aw200xx_led *led; - u32 source, imax; + struct aw200xx_led_data *led = &leds[count++]; + u32 source; + + led->num = -1; ret = fwnode_property_read_u32(child, "reg", &source); if (ret) { @@ -442,29 +1256,94 @@ static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip) continue; } + max_source = max_t(int, max_source, source); + ret = fwnode_property_read_u32(child, "led-max-microamp", - &imax); + &led->imax); if (ret) { dev_info(&chip->client->dev, "DT property led-max-microamp is missing\n"); - } else if (imax < current_min || imax > current_max) { + } else { + min_uA = min(min_uA, led->imax); + } + + led->default_state = led_init_default_state_get(child); + led->num = source; + } + + if (max_source < 0) + return; + + chip->display_rows = aw200xx_get_display_rows(chip, max_source); + current_max = aw200xx_imax_from_global(chip, AW200XX_IMAX_MAX_uA); + current_min = aw200xx_imax_from_global(chip, AW200XX_IMAX_MIN_uA); + + for (i = 0; i < count; i++) { + struct aw200xx_led_data *led = &leds[i]; + + if (led->num < 0) + continue; + + if (led->imax && + (led->imax < current_min || led->imax > current_max)) { dev_err(dev, "Invalid value %u for led-max-microamp\n", - imax); + led->imax); chip->num_leds--; + led->num = -1; continue; - } else { - min_uA = min(min_uA, imax); } + if (led->default_state == LEDS_DEFSTATE_KEEP) + *keep_state = true; + } + + if (min_uA == U32_MAX) + min_uA = aw200xx_imax_from_global(chip, AW200XX_IMAX_DEFAULT_uA); + + chip->imax = min_uA; +} + +static void aw200xx_probe_register_leds(struct device *dev, struct aw200xx *chip, + const struct aw200xx_led_data *led_data, + bool keep_state) +{ + int i = 0, data_index = 0; + + if (chip->queue_mode) + dev_attr_dim.store = dim_store_queue; + + device_for_each_child_node_scoped(dev, child) { + struct led_init_data init_data = {}; + struct aw200xx_led *led; + const struct aw200xx_led_data *data = &led_data[data_index++]; + int ret; + + if (data->num < 0) + continue; + led = &chip->leds[i]; led->dim = -1; - led->num = source; + led->num = data->num; led->chip = chip; - led->cdev.brightness_set_blocking = aw200xx_brightness_set; + led->cdev.brightness_set_blocking = chip->queue_mode ? + aw200xx_brightness_set_queue : aw200xx_brightness_set; led->cdev.max_brightness = AW200XX_FADE_MAX; led->cdev.groups = dim_groups; init_data.fwnode = child; + switch (led_data->default_state) { + case LEDS_DEFSTATE_ON: + led->cdev.brightness = AW200XX_FADE_MAX; + aw200xx_brightness_set(&led->cdev, AW200XX_FADE_MAX); + break; + case LEDS_DEFSTATE_OFF: + if (keep_state) + aw200xx_brightness_set(&led->cdev, 0); + break; + default: + break; + } + ret = devm_led_classdev_register_ext(dev, &led->cdev, &init_data); if (ret) @@ -472,16 +1351,317 @@ static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip) i++; } + chip->num_pattern_leds += chip->num_leds; + + return; +} + +static int aw200xx_bl_update_status(struct backlight_device *bl) +{ + struct aw200xx *chip = bl_get_data(bl); + int brightness = bl->props.brightness; + + if (bl->props.power != FB_BLANK_UNBLANK || + bl->props.state & BL_CORE_FBBLANK) + brightness = 0; + + mutex_lock(&chip->mutex); + + chip->fb.fade_scale = brightness; + aw200xx_fb_add_screen_dirty_range(chip); - if (!chip->num_leds) + mutex_unlock(&chip->mutex); + + schedule_delayed_work(&chip->drawer, 0); + + return 0; +} + +static bool aw200xx_bl_controls_device(struct backlight_device *bl, + struct device *display_dev) +{ + const struct aw200xx *chip = bl_get_data(bl); + + return display_dev == &chip->client->dev; +} + +static const struct backlight_ops aw200xx_bl_ops = { + .update_status = aw200xx_bl_update_status, + .controls_device = aw200xx_bl_controls_device, +}; + +static int aw200xx_fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct aw200xx *chip = info->par; + + mutex_lock(&chip->mutex); + chip->fb.offset_y = var->yoffset; + mutex_unlock(&chip->mutex); + + return 0; +} + +static int aw200xx_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct aw200xx *chip = info->par; + + return vm_insert_page(vma, vma->vm_start, + virt_to_page(chip->fb.buffer)); +} + +static int aw200xx_fb_open(struct fb_info *info, int user) +{ + struct aw200xx *chip = info->par; + int i; + + mutex_lock(&chip->mutex); + + for (i = 0; i < AW200XX_PATTERN_MAX; i++) + if (find_next_bit(chip->pattern_leds[i], chip->num_pattern_leds, + chip->num_leds) != chip->num_pattern_leds) { + mutex_unlock(&chip->mutex); + return -EBUSY; + } + + atomic_inc(&chip->fb.open_count); + mutex_unlock(&chip->mutex); + + schedule_delayed_work(&chip->drawer, 0); + + return 0; +} + +static int aw200xx_fb_release(struct fb_info *info, int user) +{ + struct aw200xx *chip = info->par; + + if (atomic_dec_and_test(&chip->fb.open_count)) { + mutex_lock(&chip->mutex); + aw200xx_fb_add_screen_dirty_range(chip); + aw200xx_fb_queue(chip); + mutex_unlock(&chip->mutex); + } + return 0; +} + +static const struct fb_fix_screeninfo aw200xx_fb_fix = { + .id = "aw200xx-fb", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_PSEUDOCOLOR, + .accel = FB_ACCEL_NONE, +}; + +static const struct fb_var_screeninfo aw200xx_fb_var = { + .bits_per_pixel = 8, + .grayscale = 1, + .red = { 0, 255, 0 }, + .green = { 0, 255, 0 }, + .blue = { 0, 255, 0 }, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static const struct fb_ops aw200xx_fb_ops = { + .owner = THIS_MODULE, + .fb_open = aw200xx_fb_open, + .fb_release = aw200xx_fb_release, + .fb_read = fb_sys_read, + .fb_write = fb_sys_write, + .fb_fillrect = sys_fillrect, + .fb_copyarea = sys_copyarea, + .fb_imageblit = sys_imageblit, + .fb_mmap = aw200xx_fb_mmap, + .fb_pan_display = aw200xx_fb_pan_display, +}; + +static void aw200xx_probe_find_fb_reg_range(struct aw200xx *chip) +{ + struct aw200xx_fb *fbdev = &chip->fb; + int i; + + fbdev->max_reg = 0; + fbdev->min_reg = chip->cdef->channels - 1; + + for (i = 0; i < fbdev->height * fbdev->width; i++) { + const uint8_t reg = fbdev->map[i]; + + fbdev->max_reg = max(fbdev->max_reg, reg); + fbdev->min_reg = min(fbdev->min_reg, reg); + } +} + +static int aw200xx_probe_add_backlight(struct aw200xx *chip, struct device *dev) +{ + struct aw200xx_fb *fbdev = &chip->fb; + struct device_node *np = dev_of_node(dev); + struct backlight_properties bl_props; + u32 brightness = AW200XX_FADE_MAX; + + of_property_read_u32(np, "fb-backlight", &brightness); + if (brightness > AW200XX_FADE_MAX) { + dev_err(dev, "property 'fb-backlight' is out of range [0-%d]\n", + (int)AW200XX_FADE_MAX); return -EINVAL; + } + + fbdev->fade_scale = brightness; - if (min_uA == UINT_MAX) { - min_uA = aw200xx_imax_from_global(chip, - AW200XX_IMAX_DEFAULT_uA); + memset(&bl_props, 0, sizeof(struct backlight_properties)); + bl_props.type = BACKLIGHT_RAW; + bl_props.max_brightness = AW200XX_FADE_MAX; + bl_props.brightness = brightness; + + fbdev->bl = devm_backlight_device_register(dev, "aw200xx-bl", + dev, chip, + &aw200xx_bl_ops, + &bl_props); + if (IS_ERR(fbdev->bl)) { + dev_err(dev, "failed to register backlight\n"); + return PTR_ERR(fbdev->bl); } - return aw200xx_set_imax(chip, min_uA); + return 0; +} + +static int dev_err_prop(struct device *dev, const char *prop_name, int err) +{ + dev_err(dev, "Failed to read '%s' property: %d\n", + prop_name, err); + return err; +} + +static int aw200xx_probe_dt_fb(struct aw200xx *chip, struct device *dev) +{ + struct aw200xx_fb *fbdev = &chip->fb; + struct device_node *np = dev_of_node(dev); + u32 imax, size; + int ret, display_rows; + + if (!of_property_present(np, "fb-map")) + return 0; + + ret = of_property_read_u32(np, "fb-width", &fbdev->width); + if (ret) + return dev_err_prop(dev, "fb-width", -EINVAL); + + ret = of_property_read_u32(np, "fb-height", &fbdev->height); + if (ret) + return dev_err_prop(dev, "fb-height", -EINVAL); + + ret = of_property_read_u32(np, "fb-refresh-rate", &fbdev->refresh_rate); + if (ret) + return dev_err_prop(dev, "fb-refresh-rate", -EINVAL); + + size = of_property_count_u8_elems(np, "fb-map"); + if (size < 0) + return dev_err_prop(dev, "fb-map", -EINVAL); + + if (size > chip->cdef->channels) { + dev_err(dev, "Display size %d is greater than led count\n", size); + return -EINVAL; + } + + if (size != fbdev->height * fbdev->width) { + dev_err(dev, "Map size (%d) is not equal to display size\n", size); + return -EINVAL; + } + + fbdev->map = devm_kzalloc(dev, size, GFP_KERNEL); + if (!fbdev->map) + return -ENOMEM; + + ret = of_property_read_u8_array(np, "fb-map", fbdev->map, size); + if (ret) + return ret; + + aw200xx_probe_find_fb_reg_range(chip); + display_rows = aw200xx_get_display_rows(chip, fbdev->max_reg); + chip->display_rows = max(display_rows, chip->display_rows); + + ret = of_property_read_u32(np, "fb-led-max-microamp", &imax); + if (!ret) { + const u32 current_min = + aw200xx_imax_from_global(chip, AW200XX_IMAX_MIN_uA); + const u32 current_max = + aw200xx_imax_from_global(chip, AW200XX_IMAX_MAX_uA); + if (imax < current_min || imax > current_max) { + dev_err(dev, "Framebuffer led-max-microamp is out of range\n"); + return -EINVAL; + } + chip->imax = min(chip->imax, imax); + } else { + dev_info(dev, "DT property led-max-microamp is missing\n"); + } + + chip->queue_mode = true; + chip->fb.num_leds = size; + + return 0; +} + +static int aw200xx_probe_register_fb(struct device *dev, struct aw200xx *chip) +{ + struct aw200xx_fb *fbdev = &chip->fb; + const int size = fbdev->width * fbdev->width; + int ret; + + if (!fbdev->num_leds) + return 0; + + if (size * FB_BUFFER_COUNT > PAGE_SIZE) + return -EINVAL; + + fbdev->buffer = (uint8_t *)get_zeroed_page(GFP_KERNEL); + if (!fbdev->buffer) + return -ENOMEM; + + fbdev->info = framebuffer_alloc(0, dev); + if (!fbdev->info) { + ret = -ENOMEM; + goto err_fb_alloc; + } + + fbdev->info->fbops = &aw200xx_fb_ops; + fbdev->info->screen_buffer = (char __iomem *)fbdev->buffer; + fbdev->info->screen_size = size; + fbdev->info->fix = aw200xx_fb_fix; + fbdev->info->var = aw200xx_fb_var; + fbdev->info->pseudo_palette = NULL; + fbdev->info->flags = FBINFO_VIRTFB; + fbdev->info->par = chip; + + fbdev->info->fix.smem_len = size * FB_BUFFER_COUNT; + fbdev->info->fix.line_length = fbdev->width; + fbdev->info->fix.ypanstep = fbdev->height; + + fbdev->info->var.xres = fbdev->width; + fbdev->info->var.yres = fbdev->height; + fbdev->info->var.xres_virtual = fbdev->width; + fbdev->info->var.yres_virtual = fbdev->height * FB_BUFFER_COUNT; + + ret = register_framebuffer(fbdev->info); + if (ret) { + dev_err(dev, "Register framebuffer failed\n"); + goto err_register_fb; + } + + ret = aw200xx_probe_add_backlight(chip, dev); + if (ret) + goto err_probe_bl; + + chip->num_pattern_leds += fbdev->num_leds; + + return 0; + +err_probe_bl: + unregister_framebuffer(fbdev->info); + +err_register_fb: + framebuffer_release(fbdev->info); + +err_fb_alloc: + free_page((unsigned long) fbdev->buffer); + + return ret; } static const struct regmap_range_cfg aw200xx_ranges[] = { @@ -508,6 +1688,7 @@ static const struct regmap_access_table aw200xx_readable_table = { static const struct regmap_range aw200xx_readonly_ranges[] = { regmap_reg_range(AW200XX_REG_IDR, AW200XX_REG_IDR), + regmap_reg_range(AW200XX_REG_ISRFLT, AW200XX_REG_ISRFLT), }; static const struct regmap_access_table aw200xx_writeable_table = { @@ -515,6 +1696,16 @@ static const struct regmap_access_table aw200xx_writeable_table = { .n_no_ranges = ARRAY_SIZE(aw200xx_readonly_ranges), }; +static const struct regmap_range aw200xx_volatile_registers[] = { + regmap_reg_range(AW200XX_REG_ISRFLT, AW200XX_REG_ISRFLT), + regmap_reg_range(AW200XX_REG_PATGO, AW200XX_REG_PATGO), +}; + +static const struct regmap_access_table aw200xx_volatile_table = { + .yes_ranges = aw200xx_volatile_registers, + .n_yes_ranges = ARRAY_SIZE(aw200xx_volatile_registers), +}; + static const struct regmap_config aw200xx_regmap_config = { .reg_bits = 8, .val_bits = 8, @@ -523,6 +1714,7 @@ static const struct regmap_config aw200xx_regmap_config = { .num_ranges = ARRAY_SIZE(aw200xx_ranges), .rd_table = &aw200xx_readable_table, .wr_table = &aw200xx_writeable_table, + .volatile_table = &aw200xx_volatile_table, .cache_type = REGCACHE_MAPLE, .disable_locking = true, }; @@ -541,6 +1733,8 @@ static int aw200xx_probe(struct i2c_client *client) { const struct aw200xx_chipdef *cdef; struct aw200xx *chip; + struct aw200xx_led_data *leds; + bool keep_state = false; int count; int ret; @@ -563,6 +1757,40 @@ static int aw200xx_probe(struct i2c_client *client) chip->client = client; i2c_set_clientdata(client, chip); + leds = devm_kcalloc(&client->dev, count, sizeof(struct aw200xx_led_data), + GFP_KERNEL); + if (!leds) + return -ENOMEM; + + aw200xx_probe_dt_leds(&client->dev, chip, leds, &keep_state); + ret = aw200xx_probe_dt_fb(chip, &client->dev); + if (ret) { + dev_err_probe(&client->dev, ret, "Framebuffer probe failed\n"); + return ret; + } + + if (!chip->num_leds && !chip->fb.num_leds) + return dev_err_probe(&client->dev, -EINVAL, + "No valid led definitions found\n"); + + if (chip->queue_mode) { + chip->dim_page.buf = devm_kzalloc(&client->dev, + chip->cdef->channels, GFP_KERNEL); + if (!chip->dim_page.buf) + return -ENOMEM; + + chip->dim_page.page = AW200XX_REG(AW200XX_PAGE1, 0); + + chip->fade_page.buf = devm_kzalloc(&client->dev, + chip->cdef->channels, GFP_KERNEL); + if (!chip->fade_page.buf) + return -ENOMEM; + + chip->fade_page.page = AW200XX_REG(AW200XX_PAGE2, 0); + + INIT_DELAYED_WORK(&chip->drawer, aw200xx_update); + } + chip->regmap = devm_regmap_init_i2c(client, &aw200xx_regmap_config); if (IS_ERR(chip->regmap)) return PTR_ERR(chip->regmap); @@ -587,29 +1815,53 @@ static int aw200xx_probe(struct i2c_client *client) if (ret) return ret; - /* 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; + if (!keep_state) { + ret = aw200xx_chip_reset(chip); + if (ret) + return ret; + } ret = devm_add_action(&client->dev, aw200xx_chip_reset_action, chip); if (ret) - goto out_unlock; + return ret; + + if (!keep_state) { + ret = aw200xx_chip_init(chip); + if (ret) + return ret; + } - ret = aw200xx_probe_fw(&client->dev, chip); + ret = aw200xx_setup_interrupts(chip); if (ret) - goto out_unlock; + return ret; - ret = aw200xx_chip_init(chip); + aw200xx_probe_register_leds(&client->dev, chip, leds, keep_state); -out_unlock: + ret = aw200xx_probe_register_fb(&client->dev, chip); if (ret) - aw200xx_disable(chip); + return ret; - mutex_unlock(&chip->mutex); - return ret; + devm_kfree(&client->dev, leds); + + return 0; +} + +static void aw200xx_remove(struct i2c_client *client) +{ + struct aw200xx *chip = i2c_get_clientdata(client); + struct aw200xx_fb *fbdev = &chip->fb; + + if (fbdev) { + mutex_lock(&chip->mutex); + devm_backlight_device_unregister(&client->dev, fbdev->bl); + unregister_framebuffer(fbdev->info); + framebuffer_release(fbdev->info); + free_page((unsigned long) fbdev->buffer); + mutex_unlock(&chip->mutex); + } + + if (chip->queue_mode) + disable_delayed_work_sync(&chip->drawer); } static const struct aw200xx_chipdef aw20036_cdef = { @@ -658,8 +1910,10 @@ static struct i2c_driver aw200xx_driver = { .driver = { .name = "aw200xx", .of_match_table = aw200xx_match_table, + .dev_groups = aw200xx_pattern_groups, }, .probe = aw200xx_probe, + .remove = aw200xx_remove, .id_table = aw200xx_id, }; module_i2c_driver(aw200xx_driver); |
