| --- a/drivers/leds/leds-iei-wt61p803-puzzle.c |
| +++ b/drivers/leds/leds-iei-wt61p803-puzzle.c |
| @@ -9,9 +9,13 @@ |
| #include <linux/mfd/iei-wt61p803-puzzle.h> |
| #include <linux/mod_devicetable.h> |
| #include <linux/module.h> |
| +#include <linux/of.h> |
| #include <linux/platform_device.h> |
| #include <linux/property.h> |
| #include <linux/slab.h> |
| +#include <linux/workqueue.h> |
| + |
| +#define IEI_LEDS_MAX 4 |
| |
| enum iei_wt61p803_puzzle_led_state { |
| IEI_LED_OFF = 0x30, |
| @@ -33,7 +37,11 @@ struct iei_wt61p803_puzzle_led { |
| struct iei_wt61p803_puzzle *mcu; |
| unsigned char response_buffer[IEI_WT61P803_PUZZLE_BUF_SIZE]; |
| struct mutex lock; /* mutex to protect led_power_state */ |
| + struct work_struct work; |
| int led_power_state; |
| + int id; |
| + u8 blinking; |
| + bool active_low; |
| }; |
| |
| static inline struct iei_wt61p803_puzzle_led *cdev_to_iei_wt61p803_puzzle_led |
| @@ -51,10 +59,18 @@ static int iei_wt61p803_puzzle_led_brigh |
| size_t reply_size; |
| int ret; |
| |
| + if (priv->blinking) { |
| + if (brightness == LED_OFF) |
| + priv->blinking = 0; |
| + else |
| + return 0; |
| + } |
| + |
| led_power_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START; |
| led_power_cmd[1] = IEI_WT61P803_PUZZLE_CMD_LED; |
| - led_power_cmd[2] = IEI_WT61P803_PUZZLE_CMD_LED_POWER; |
| - led_power_cmd[3] = brightness == LED_OFF ? IEI_LED_OFF : IEI_LED_ON; |
| + led_power_cmd[2] = IEI_WT61P803_PUZZLE_CMD_LED_SET(priv->id); |
| + led_power_cmd[3] = ((brightness == LED_OFF) ^ priv->active_low) ? |
| + IEI_LED_OFF : priv->blinking?priv->blinking:IEI_LED_ON; |
| |
| ret = iei_wt61p803_puzzle_write_command(priv->mcu, led_power_cmd, |
| sizeof(led_power_cmd), |
| @@ -90,39 +106,166 @@ static enum led_brightness iei_wt61p803_ |
| return led_state; |
| } |
| |
| +static void iei_wt61p803_puzzle_led_apply_blink(struct work_struct *work) |
| +{ |
| + struct iei_wt61p803_puzzle_led *priv = container_of(work, struct iei_wt61p803_puzzle_led, work); |
| + unsigned char led_blink_cmd[5] = {}; |
| + unsigned char resp_buf[IEI_WT61P803_PUZZLE_BUF_SIZE]; |
| + size_t reply_size; |
| + |
| + led_blink_cmd[0] = IEI_WT61P803_PUZZLE_CMD_HEADER_START; |
| + led_blink_cmd[1] = IEI_WT61P803_PUZZLE_CMD_LED; |
| + led_blink_cmd[2] = IEI_WT61P803_PUZZLE_CMD_LED_SET(priv->id); |
| + led_blink_cmd[3] = priv->blinking; |
| + |
| + iei_wt61p803_puzzle_write_command(priv->mcu, led_blink_cmd, |
| + sizeof(led_blink_cmd), |
| + resp_buf, |
| + &reply_size); |
| + |
| + return; |
| +} |
| + |
| +static int iei_wt61p803_puzzle_led_set_blink(struct led_classdev *cdev, |
| + unsigned long *delay_on, |
| + unsigned long *delay_off) |
| +{ |
| + struct iei_wt61p803_puzzle_led *priv = cdev_to_iei_wt61p803_puzzle_led(cdev); |
| + u8 blink_mode = 0; |
| + int ret = 0; |
| + |
| + /* set defaults */ |
| + if (!*delay_on && !*delay_off) { |
| + *delay_on = 500; |
| + *delay_off = 500; |
| + } |
| + |
| + /* minimum delay for soft-driven blinking is 100ms to keep load low */ |
| + if (*delay_on < 100) |
| + *delay_on = 100; |
| + |
| + if (*delay_off < 100) |
| + *delay_off = 100; |
| + |
| + /* offload blinking to hardware, if possible */ |
| + if (*delay_on != *delay_off) { |
| + ret = -EINVAL; |
| + } else if (*delay_on == 100) { |
| + blink_mode = IEI_LED_BLINK_5HZ; |
| + *delay_on = 100; |
| + *delay_off = 100; |
| + } else if (*delay_on <= 500) { |
| + blink_mode = IEI_LED_BLINK_1HZ; |
| + *delay_on = 500; |
| + *delay_off = 500; |
| + } else { |
| + ret = -EINVAL; |
| + } |
| + |
| + mutex_lock(&priv->lock); |
| + priv->blinking = blink_mode; |
| + mutex_unlock(&priv->lock); |
| + |
| + if (blink_mode) |
| + schedule_work(&priv->work); |
| + |
| + return ret; |
| +} |
| + |
| + |
| +static int iei_wt61p803_puzzle_led_set_dt_default(struct led_classdev *cdev, |
| + struct device_node *np) |
| +{ |
| + const char *state; |
| + int ret = 0; |
| + |
| + state = of_get_property(np, "default-state", NULL); |
| + if (state) { |
| + if (!strcmp(state, "on")) { |
| + ret = |
| + iei_wt61p803_puzzle_led_brightness_set_blocking( |
| + cdev, cdev->max_brightness); |
| + } else { |
| + ret = iei_wt61p803_puzzle_led_brightness_set_blocking( |
| + cdev, LED_OFF); |
| + } |
| + } |
| + |
| + return ret; |
| +} |
| + |
| static int iei_wt61p803_puzzle_led_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| + struct device_node *np = dev_of_node(dev); |
| + struct device_node *child; |
| struct iei_wt61p803_puzzle *mcu = dev_get_drvdata(dev->parent); |
| struct iei_wt61p803_puzzle_led *priv; |
| - struct led_init_data init_data = {}; |
| - struct fwnode_handle *child; |
| int ret; |
| + u32 reg; |
| |
| - if (device_get_child_node_count(dev) != 1) |
| + if (device_get_child_node_count(dev) > IEI_LEDS_MAX) |
| return -EINVAL; |
| |
| - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); |
| - if (!priv) |
| - return -ENOMEM; |
| - |
| - priv->mcu = mcu; |
| - priv->led_power_state = 1; |
| - mutex_init(&priv->lock); |
| - dev_set_drvdata(dev, priv); |
| - |
| - child = device_get_next_child_node(dev, NULL); |
| - init_data.fwnode = child; |
| - |
| - priv->cdev.brightness_set_blocking = iei_wt61p803_puzzle_led_brightness_set_blocking; |
| - priv->cdev.brightness_get = iei_wt61p803_puzzle_led_brightness_get; |
| - priv->cdev.max_brightness = 1; |
| + for_each_available_child_of_node(np, child) { |
| + struct led_init_data init_data = {}; |
| |
| - ret = devm_led_classdev_register_ext(dev, &priv->cdev, &init_data); |
| - if (ret) |
| - dev_err(dev, "Could not register LED\n"); |
| + ret = of_property_read_u32(child, "reg", ®); |
| + if (ret) { |
| + dev_err(dev, "Failed to read led 'reg' property\n"); |
| + goto put_child_node; |
| + } |
| + |
| + if (reg > IEI_LEDS_MAX) { |
| + dev_err(dev, "Invalid led reg %u\n", reg); |
| + ret = -EINVAL; |
| + goto put_child_node; |
| + } |
| + |
| + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); |
| + if (!priv) { |
| + ret = -ENOMEM; |
| + goto put_child_node; |
| + } |
| + |
| + mutex_init(&priv->lock); |
| + |
| + dev_set_drvdata(dev, priv); |
| + |
| + if (of_property_read_bool(child, "active-low")) |
| + priv->active_low = true; |
| + |
| + priv->mcu = mcu; |
| + priv->id = reg; |
| + priv->led_power_state = 1; |
| + priv->blinking = 0; |
| + init_data.fwnode = of_fwnode_handle(child); |
| + |
| + priv->cdev.brightness_set_blocking = iei_wt61p803_puzzle_led_brightness_set_blocking; |
| + priv->cdev.brightness_get = iei_wt61p803_puzzle_led_brightness_get; |
| + priv->cdev.blink_set = iei_wt61p803_puzzle_led_set_blink; |
| + |
| + priv->cdev.max_brightness = 1; |
| + |
| + INIT_WORK(&priv->work, iei_wt61p803_puzzle_led_apply_blink); |
| + |
| + ret = iei_wt61p803_puzzle_led_set_dt_default(&priv->cdev, child); |
| + if (ret) { |
| + dev_err(dev, "Could apply default from DT\n"); |
| + goto put_child_node; |
| + } |
| + |
| + ret = devm_led_classdev_register_ext(dev, &priv->cdev, &init_data); |
| + if (ret) { |
| + dev_err(dev, "Could not register LED\n"); |
| + goto put_child_node; |
| + } |
| + } |
| + |
| + return ret; |
| |
| - fwnode_handle_put(child); |
| +put_child_node: |
| + of_node_put(child); |
| return ret; |
| } |
| |
| --- a/include/linux/mfd/iei-wt61p803-puzzle.h |
| +++ b/include/linux/mfd/iei-wt61p803-puzzle.h |
| @@ -36,7 +36,7 @@ |
| #define IEI_WT61P803_PUZZLE_CMD_FUNCTION_OTHER_POWER_LOSS 0x41 /* A */ |
| |
| #define IEI_WT61P803_PUZZLE_CMD_LED 0x52 /* R */ |
| -#define IEI_WT61P803_PUZZLE_CMD_LED_POWER 0x31 /* 1 */ |
| +#define IEI_WT61P803_PUZZLE_CMD_LED_SET(n) (0x30 | (n)) |
| |
| #define IEI_WT61P803_PUZZLE_CMD_TEMP 0x54 /* T */ |
| #define IEI_WT61P803_PUZZLE_CMD_TEMP_ALL 0x41 /* A */ |
| --- a/drivers/mfd/iei-wt61p803-puzzle.c |
| +++ b/drivers/mfd/iei-wt61p803-puzzle.c |
| @@ -176,6 +176,9 @@ static int iei_wt61p803_puzzle_recv_buf( |
| struct iei_wt61p803_puzzle *mcu = serdev_device_get_drvdata(serdev); |
| int ret; |
| |
| + print_hex_dump_debug("puzzle-mcu rx: ", DUMP_PREFIX_NONE, |
| + 16, 1, data, size, false); |
| + |
| ret = iei_wt61p803_puzzle_process_resp(mcu, data, size); |
| /* Return the number of processed bytes if function returns error, |
| * discard the remaining incoming data, since the frame this data |
| @@ -246,6 +249,9 @@ int iei_wt61p803_puzzle_write_command(st |
| |
| cmd[size - 1] = iei_wt61p803_puzzle_checksum(cmd, size - 1); |
| |
| + print_hex_dump_debug("puzzle-mcu tx: ", DUMP_PREFIX_NONE, |
| + 16, 1, cmd, size, false); |
| + |
| /* Initialize reply struct */ |
| reinit_completion(&mcu->reply->received); |
| mcu->reply->size = 0; |