diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c index 5142efea2..a9d0ca187 100644 --- a/drivers/leds/leds-aw200xx.c +++ b/drivers/leds/leds-aw200xx.c @@ -71,6 +71,10 @@ #define AW200XX_REG_DSIZE AW200XX_REG(AW200XX_PAGE0, 0x80) #define AW200XX_DSIZE_COLUMNS_MAX 12 +#define AW200XX_REG_LEDON_BASE AW200XX_REG(AW200XX_PAGE0, 0x31) +#define AW200XX_LEDON_PER_REG 6 +#define AW200XX_LEDON_REGS(ch) DIV_ROUND_UP((ch)->cdef->channels, AW200XX_LEDON_PER_REG) + #define AW200XX_LED2REG(x, columns) \ ((x) + (((x) / (columns)) * (AW200XX_DSIZE_COLUMNS_MAX - (columns)))) @@ -116,6 +120,8 @@ struct aw200xx { struct mutex mutex; u32 num_leds; u32 display_rows; + u32 min_uA; + u8 ledon_shadow[6]; /* AW20036: LEDON0..LEDON5 */ struct aw200xx_led leds[]; }; @@ -178,6 +184,41 @@ static struct attribute *dim_attrs[] = { }; ATTRIBUTE_GROUPS(dim); +static int aw200xx_ledon_write(struct aw200xx *chip, u32 idx) +{ + return regmap_write(chip->regmap, AW200XX_REG_LEDON_BASE + idx, + chip->ledon_shadow[idx]); +} + +static int aw200xx_set_ledon(struct aw200xx *chip, u32 led_num, bool on) +{ + u32 idx = led_num / AW200XX_LEDON_PER_REG; + u32 bit = led_num % AW200XX_LEDON_PER_REG; + + if (idx >= ARRAY_SIZE(chip->ledon_shadow)) + return -EINVAL; + + if (on) + chip->ledon_shadow[idx] |= BIT(bit); + else + chip->ledon_shadow[idx] &= ~BIT(bit); + + return aw200xx_ledon_write(chip, idx); +} + +static int aw200xx_all_leds_off(struct aw200xx *chip) +{ + int i, ret; + + for (i = 0; i < AW200XX_LEDON_REGS(chip); i++) { + chip->ledon_shadow[i] = 0x00; + ret = aw200xx_ledon_write(chip, i); + if (ret) + return ret; + } + return 0; +} + static int aw200xx_brightness_set(struct led_classdev *cdev, enum led_brightness brightness) { @@ -189,6 +230,16 @@ static int aw200xx_brightness_set(struct led_classdev *cdev, mutex_lock(&chip->mutex); + /* Turn channel on/off deterministically */ + ret = aw200xx_set_ledon(chip, led->num, brightness != 0); + if (ret) + goto out_unlock; + + if (brightness == 0) { + ret = 0; + goto out_unlock; + } + reg = AW200XX_REG_DIM(led->num, chip->cdef->display_size_columns); dim = led->dim; @@ -201,12 +252,10 @@ static int aw200xx_brightness_set(struct led_classdev *cdev, if (ret) goto out_unlock; - ret = regmap_write(chip->regmap, - AW200XX_REG_DIM2FADE(reg), brightness); + ret = regmap_write(chip->regmap, AW200XX_REG_DIM2FADE(reg), brightness); out_unlock: mutex_unlock(&chip->mutex); - return ret; } @@ -337,10 +386,13 @@ static int aw200xx_chip_init(const struct aw200xx *const chip) if (ret) return ret; - return regmap_update_bits(chip->regmap, AW200XX_REG_GCCR, - AW200XX_GCCR_ALLON, AW200XX_GCCR_ALLON); + /* page1..page5 accessible ~200us after ACTIVE :contentReference[oaicite:3]{index=3} */ + usleep_range(300, 600); + + return 0; } + static int aw200xx_chip_check(const struct aw200xx *const chip) { struct device *dev = &chip->client->dev; @@ -442,9 +494,147 @@ static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip) AW200XX_IMAX_DEFAULT_uA); } + chip->min_uA = min_uA; + return aw200xx_set_imax(chip, min_uA); } +static int aw200xx_reset(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct aw200xx *chip = i2c_get_clientdata(client); + int ret, i; + int retry_count = 0; + const int max_retries = 10; + const int retry_delay_ms = 50; + + /* Wait for chip to stabilize after power-on */ + msleep(100); + + mutex_lock(&chip->mutex); + + /* Re-enable cache writes to hardware */ + regcache_cache_only(chip->regmap, false); + + /* Retry chip check if not ready */ + while (retry_count < max_retries) { + ret = aw200xx_chip_check(chip); + if (ret == 0) { + if (retry_count > 0) + dev_info(dev, "Chip ready after %d retries\n", retry_count); + break; + } + + retry_count++; + if (retry_count < max_retries) { + dev_warn(dev, "Chip check failed (attempt %d/%d), retrying...\n", + retry_count, max_retries); + mutex_unlock(&chip->mutex); + msleep(retry_delay_ms); + mutex_lock(&chip->mutex); + } else { + dev_err(dev, "Chip check failed after %d retries: %d\n", + max_retries, ret); + goto out; + } + } + + ret = aw200xx_chip_reset(chip); + if (ret) { + dev_err(dev, "Chip reset failed: %d\n", ret); + goto out; + } + + ret = aw200xx_set_imax(chip, chip->min_uA); + if (ret) { + dev_err(dev, "Set imax failed: %d\n", ret); + goto out; + } + + ret = aw200xx_chip_init(chip); + if (ret) { + dev_err(dev, "Chip init failed: %d\n", ret); + goto out; + } + + /* Start from a known safe state: everything OFF */ + ret = aw200xx_all_leds_off(chip); + if (ret) { + dev_err(dev, "All LEDs off failed: %d\n", ret); + goto out; + } + + /* Restore LED state from kernel brightness */ + for (i = 0; i < chip->num_leds; i++) { + struct aw200xx_led *led = &chip->leds[i]; + enum led_brightness br = led->cdev.brightness; + u32 columns = chip->cdef->display_size_columns; + u32 reg; + int dim; + + /* Enable/disable channel based on brightness */ + ret = aw200xx_set_ledon(chip, led->num, br != 0); + if (ret) { + dev_err(dev, "Failed to set LEDON for LED %d: %d\n", i, ret); + continue; + } + + if (br == 0) + continue; + + /* Restore DIM page 1 if set */ + if (led->dim >= 0) { + ret = regmap_write(chip->regmap, + AW200XX_REG_DIM_PAGE1(led->num, columns), + led->dim); + if (ret) { + dev_err(dev, "Failed to restore LED %d page1 dim: %d\n", i, ret); + continue; + } + } + + /* Restore DIM and FADE (page 4) */ + reg = AW200XX_REG_DIM(led->num, columns); + dim = led->dim; + if (dim < 0) + dim = max_t(int, br / (AW200XX_FADE_MAX / AW200XX_DIM_MAX), 1); + + ret = regmap_write(chip->regmap, reg, dim); + if (ret) { + dev_err(dev, "Failed to restore LED %d dim: %d\n", i, ret); + continue; + } + + ret = regmap_write(chip->regmap, AW200XX_REG_DIM2FADE(reg), br); + if (ret) { + dev_err(dev, "Failed to restore LED %d fade: %d\n", i, ret); + continue; + } + } + + ret = 0; /* Success even if some LEDs failed */ + +out: + mutex_unlock(&chip->mutex); + return ret; +} + +static ssize_t aw200xx_reset_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + int val; + + if (kstrtoint(buf, 10, &val)) + return -EINVAL; + + // reset chip + aw200xx_reset(dev); + + return count; +} + +static DEVICE_ATTR(reset, S_IRUSR | S_IWUSR, NULL, aw200xx_reset_store); + static const struct regmap_range_cfg aw200xx_ranges[] = { { .name = "aw200xx", @@ -535,6 +725,14 @@ static int aw200xx_probe(struct i2c_client *client) goto out_unlock; ret = aw200xx_chip_init(chip); + if (ret) + goto out_unlock; + + ret = device_create_file(&client->dev, &dev_attr_reset); + if (ret) { + dev_err(&client->dev, "failed to create sysfs file: reset\n"); + goto out_unlock; + } out_unlock: mutex_unlock(&chip->mutex); @@ -583,9 +781,39 @@ static const struct of_device_id aw200xx_match_table[] = { }; MODULE_DEVICE_TABLE(of, aw200xx_match_table); +#if defined(CONFIG_PM_SLEEP) +static int aw200xx_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct aw200xx *chip = i2c_get_clientdata(client); + + mutex_lock(&chip->mutex); + + /* Mark cache as invalid since power will be cut */ + regcache_cache_only(chip->regmap, true); + regcache_mark_dirty(chip->regmap); + + mutex_unlock(&chip->mutex); + + return 0; +} + +static int aw200xx_resume(struct device *dev) +{ + int ret; + + ret = aw200xx_reset(dev); + + return ret; +} +#endif + +static SIMPLE_DEV_PM_OPS(aw200xx_pm_ops, aw200xx_suspend, aw200xx_resume); + static struct i2c_driver aw200xx_driver = { .driver = { .name = "aw200xx", + .pm = &aw200xx_pm_ops, .of_match_table = aw200xx_match_table, }, .probe = aw200xx_probe,