summaryrefslogtreecommitdiff
path: root/drivers/leds/leds-aw200xx.c
diff options
context:
space:
mode:
authorDmitry Rokosov <rockosov@rulkc.org>2026-08-14 01:40:02 +0300
committerDmitry Rokosov <rockosov@rulkc.org>2026-08-14 01:40:02 +0300
commitb1a9f82c8ec64a6c7a648b6a4ab481f1d3b9f1e4 (patch)
tree8f0964c1f46b9cf430b5fdaa2d12bcbcbe16ada5 /drivers/leds/leds-aw200xx.c
parent028ef9c96e96197026887c0f092424679298aae8 (diff)
parent0e0488eb915954f96e9ce4b40c0a9d8da6eebc19 (diff)
downloadlinux-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.c1392
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);