diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c index 0d219c1ac3b5..a5df65662a25 100644 --- a/drivers/leds/leds-is31fl32xx.c +++ b/drivers/leds/leds-is31fl32xx.c @@ -16,6 +16,8 @@ #include #include #include +#include +#include /* Used to indicate a device has no such register */ #define IS31FL32XX_REG_NONE 0xFF @@ -38,12 +40,20 @@ struct is31fl32xx_led_data { struct led_classdev cdev; u8 channel; /* 1-based, max priv->cdef->channels */ struct is31fl32xx_priv *priv; + unsigned int register_delay; + struct device *dev; + struct delayed_work register_work; + struct work_struct brightness_work; + enum led_brightness new_brightness; + struct led_init_data init_data; }; struct is31fl32xx_priv { const struct is31fl32xx_chipdef *cdef; struct i2c_client *client; unsigned int num_leds; + struct mutex led_mutex; + struct gpio_desc *reset_gpio; struct is31fl32xx_led_data leds[]; }; @@ -138,14 +148,17 @@ static const struct is31fl32xx_chipdef is31fl3216_cdef = { static int is31fl32xx_write(struct is31fl32xx_priv *priv, u8 reg, u8 val) { int ret; + int retries = 3; dev_dbg(&priv->client->dev, "writing register 0x%02X=0x%02X", reg, val); ret = i2c_smbus_write_byte_data(priv->client, reg, val); - if (ret) { + while (ret && (retries-- > 0)) { dev_err(&priv->client->dev, - "register write to 0x%02X failed (error %d)", - reg, ret); + "register write to 0x%02X failed (error %d),val=%d", + reg, ret, val); + msleep(100); + ret = i2c_smbus_write_byte_data(priv->client, reg, val); } return ret; } @@ -199,6 +212,32 @@ static int is31fl3216_software_shutdown(struct is31fl32xx_priv *priv, return is31fl32xx_write(priv, IS31FL3216_CONFIG_REG, value); } +static void is31fl32xx_brightness_work(struct work_struct *work) +{ + const struct is31fl32xx_led_data *led_data = + container_of(work, + struct is31fl32xx_led_data, + brightness_work); + const struct is31fl32xx_chipdef *cdef = led_data->priv->cdef; + u8 pwm_register_offset; + + dev_dbg(led_data->cdev.dev, "%s: %d\n", + __func__, led_data->new_brightness); + mutex_lock(&led_data->priv->led_mutex); + /* NOTE: led_data->channel is 1-based */ + if (cdef->pwm_registers_reversed) + pwm_register_offset = cdef->channels - led_data->channel; + else + pwm_register_offset = led_data->channel - 1; + + is31fl32xx_write(led_data->priv, + cdef->pwm_register_base + pwm_register_offset, + led_data->new_brightness); + + is31fl32xx_write(led_data->priv, cdef->pwm_update_reg, 0); + mutex_unlock(&led_data->priv->led_mutex); +} + /* * NOTE: A mutex is not needed in this function because: * - All referenced data is read-only after probe() @@ -220,32 +259,21 @@ static int is31fl3216_software_shutdown(struct is31fl32xx_priv *priv, * are equivalent. Poking the Update register merely applies all PWM * register writes up to that point. */ -static int is31fl32xx_brightness_set(struct led_classdev *led_cdev, +static void is31fl32xx_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) { - const struct is31fl32xx_led_data *led_data = + struct is31fl32xx_led_data *led_data = container_of(led_cdev, struct is31fl32xx_led_data, cdev); - const struct is31fl32xx_chipdef *cdef = led_data->priv->cdef; - u8 pwm_register_offset; - int ret; - dev_dbg(led_cdev->dev, "%s: %d\n", __func__, brightness); - - /* NOTE: led_data->channel is 1-based */ - if (cdef->pwm_registers_reversed) - pwm_register_offset = cdef->channels - led_data->channel; - else - pwm_register_offset = led_data->channel - 1; - - ret = is31fl32xx_write(led_data->priv, - cdef->pwm_register_base + pwm_register_offset, - brightness); - if (ret) - return ret; - - return is31fl32xx_write(led_data->priv, cdef->pwm_update_reg, 0); + led_data->new_brightness = brightness; + schedule_work(&led_data->brightness_work); } +#if 0 + /* + * we use function is31fl32xx_software_shutdown to reset the registers + * of is31fl32xx. + */ static int is31fl32xx_reset_regs(struct is31fl32xx_priv *priv) { const struct is31fl32xx_chipdef *cdef = priv->cdef; @@ -262,6 +290,7 @@ static int is31fl32xx_reset_regs(struct is31fl32xx_priv *priv) return 0; } +#endif static int is31fl32xx_software_shutdown(struct is31fl32xx_priv *priv, bool enable) @@ -288,9 +317,9 @@ static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv) const struct is31fl32xx_chipdef *cdef = priv->cdef; int ret; - ret = is31fl32xx_reset_regs(priv); + ret = is31fl32xx_software_shutdown(priv, true); if (ret) - return ret; + pr_err("%s, write to shutdown register failed\n", __func__); /* * Set enable bit for all channels. @@ -326,12 +355,14 @@ static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv) } static int is31fl32xx_parse_child_dt(const struct device *dev, - const struct device_node *child, + struct device_node *child, struct is31fl32xx_led_data *led_data) { struct led_classdev *cdev = &led_data->cdev; int ret = 0; - u32 reg; + u32 reg = -1; + u32 blink_delay_on = 0; + u32 blink_delay_off = 0; ret = of_property_read_u32(child, "reg", ®); if (ret || reg < 1 || reg > led_data->priv->cdef->channels) { @@ -342,8 +373,20 @@ static int is31fl32xx_parse_child_dt(const struct device *dev, } led_data->channel = reg; - cdev->brightness_set_blocking = is31fl32xx_brightness_set; + of_property_read_u32(child, "linux,blink-delay-on-ms", + &blink_delay_on); + cdev->blink_delay_on = (u64)blink_delay_on; + of_property_read_u32(child, "linux,blink-delay-off-ms", + &blink_delay_off); + cdev->blink_delay_off = (u64)blink_delay_off; + + of_property_read_u32(child, "linux,default-trigger-delay-ms", + &led_data->register_delay); + + cdev->brightness_set = is31fl32xx_brightness_set; + + INIT_WORK(&led_data->brightness_work, is31fl32xx_brightness_work); return 0; } @@ -361,6 +404,22 @@ static struct is31fl32xx_led_data *is31fl32xx_find_led_data( return NULL; } +static void register_classdev_delayed(struct work_struct *ws) +{ + struct is31fl32xx_led_data *led_data = + container_of(ws, struct is31fl32xx_led_data, + register_work.work); + int ret; + + ret = devm_led_classdev_register_ext(led_data->dev, &led_data->cdev, + &led_data->init_data); + if (ret) { + dev_err(led_data->dev, "failed to register PWM led for %s: %d\n", + led_data->cdev.name, ret); + return; + } +} + static int is31fl32xx_parse_dt(struct device *dev, struct is31fl32xx_priv *priv) { @@ -368,12 +427,12 @@ static int is31fl32xx_parse_dt(struct device *dev, int ret = 0; for_each_available_child_of_node(dev_of_node(dev), child) { - struct led_init_data init_data = {}; struct is31fl32xx_led_data *led_data = &priv->leds[priv->num_leds]; const struct is31fl32xx_led_data *other_led_data; led_data->priv = priv; + led_data->dev = dev; ret = is31fl32xx_parse_child_dt(dev, child, led_data); if (ret) @@ -390,14 +449,21 @@ static int is31fl32xx_parse_dt(struct device *dev, goto err; } - init_data.fwnode = of_fwnode_handle(child); + led_data->init_data.fwnode = of_fwnode_handle(child); - ret = devm_led_classdev_register_ext(dev, &led_data->cdev, - &init_data); - if (ret) { - dev_err(dev, "Failed to register LED for %pOF: %d\n", - child, ret); - goto err; + if (led_data->register_delay) { + INIT_DELAYED_WORK(&led_data->register_work, + register_classdev_delayed); + schedule_delayed_work(&led_data->register_work, + msecs_to_jiffies(led_data->register_delay)); + } else { + ret = devm_led_classdev_register_ext(dev, &led_data->cdev, + &led_data->init_data); + if (ret) { + dev_err(dev, "failed to register PWM led for %s: %d\n", + led_data->cdev.name, ret); + goto err; + } } priv->num_leds++; @@ -434,14 +500,28 @@ static int is31fl32xx_probe(struct i2c_client *client, cdef = device_get_match_data(dev); count = of_get_available_child_count(dev_of_node(dev)); - if (!count) + if (!count) { + dev_err(dev, "count is invalid\n"); return -EINVAL; + } priv = devm_kzalloc(dev, struct_size(priv, leds, count), GFP_KERNEL); if (!priv) return -ENOMEM; + priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(priv->reset_gpio)) { + int error = PTR_ERR(priv->reset_gpio); + + dev_err(dev, "Failed to get reset gpio: %d\n", error); + priv->reset_gpio = NULL; + } + + gpiod_direction_output(priv->reset_gpio, 1); + + mutex_init(&priv->led_mutex); priv->client = client; priv->cdef = cdef; i2c_set_clientdata(client, priv); @@ -459,15 +539,67 @@ static int is31fl32xx_probe(struct i2c_client *client, static void is31fl32xx_remove(struct i2c_client *client) { + int i; struct is31fl32xx_priv *priv = i2c_get_clientdata(client); - int ret; - ret = is31fl32xx_reset_regs(priv); - if (ret) - dev_err(&client->dev, "Failed to reset registers on removal (%pe)\n", - ERR_PTR(ret)); + for (i = 0; i < priv->num_leds; i++) { + struct is31fl32xx_led_data *led_data = + &priv->leds[i]; + if (led_data->register_delay) + cancel_delayed_work_sync(&led_data->register_work); + cancel_work_sync(&led_data->brightness_work); + } + + is31fl32xx_software_shutdown(priv, true); } +static void is31fl32xx_shutdown(struct i2c_client *client) +{ + int i; + struct is31fl32xx_priv *priv = i2c_get_clientdata(client); + + for (i = 0; i < priv->num_leds; i++) { + struct is31fl32xx_led_data *led_data = + &priv->leds[i]; + if (led_data->register_delay) + cancel_delayed_work_sync(&led_data->register_work); + cancel_work_sync(&led_data->brightness_work); + } + + is31fl32xx_software_shutdown(priv, true); + gpiod_set_value(priv->reset_gpio, 0); +} + +#ifdef CONFIG_PM_SLEEP +static int is31fl32xx_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct is31fl32xx_priv *priv = i2c_get_clientdata(client); + + if (!priv->reset_gpio) { + return is31fl3216_software_shutdown(priv, + IS31FL3216_CONFIG_SSD_DISABLE); + } else { + gpiod_set_value(priv->reset_gpio, 0); + return 0; + } +} + +static int is31fl32xx_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct is31fl32xx_priv *priv = i2c_get_clientdata(client); + + if (!priv->reset_gpio) { + return is31fl3216_software_shutdown(priv, + IS31FL3216_CONFIG_SSD_ENABLE); + } else { + gpiod_set_value(priv->reset_gpio, 1); + return 0; + } +} +#endif /* CONFIG_PM_SLEEP */ + /* * i2c-core (and modalias) requires that id_table be properly filled, * even though it is not used for DeviceTree based instantiation. @@ -484,13 +616,19 @@ static const struct i2c_device_id is31fl32xx_id[] = { MODULE_DEVICE_TABLE(i2c, is31fl32xx_id); +static SIMPLE_DEV_PM_OPS(is31fl32xx_pmops, + is31fl32xx_suspend, + is31fl32xx_resume); + static struct i2c_driver is31fl32xx_driver = { .driver = { .name = "is31fl32xx", .of_match_table = of_is31fl32xx_match, + .pm = &is31fl32xx_pmops, }, .probe = is31fl32xx_probe, .remove = is31fl32xx_remove, + .shutdown = is31fl32xx_shutdown, .id_table = is31fl32xx_id, };