diff options
Diffstat (limited to 'drivers/leds')
56 files changed, 1938 insertions, 345 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 2c5fdf848210..6046dfeca16f 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -94,6 +94,19 @@ config LEDS_ARIEL Say Y to if your machine is a Dell Wyse 3020 thin client. +config LEDS_AW200XX + tristate "LED support for Awinic AW20036/AW20054/AW20072" + depends on LEDS_CLASS + depends on I2C + help + This option enables support for the AW20036/AW20054/AW20072 LED driver. + It is a 3x12/6x9/6x12 matrix LED driver programmed via + an I2C interface, up to 36/54/72 LEDs or 12/18/24 RGBs, + 3 pattern controllers for auto breathing or group dimming control. + + To compile this driver as a module, choose M here: the module + will be called leds-aw200xx. + config LEDS_AW2013 tristate "LED support for Awinic AW2013" depends on LEDS_CLASS && I2C && OF @@ -122,6 +135,17 @@ config LEDS_BCM6358 This option enables support for LEDs connected to the BCM6358 LED HW controller accessed via MMIO registers. +config LEDS_CHT_WCOVE + tristate "LED support for Intel Cherry Trail Whiskey Cove PMIC" + depends on LEDS_CLASS + depends on INTEL_SOC_PMIC_CHTWC + help + This option enables support for charger and general purpose LEDs + connected to the Intel Cherrytrail Whiskey Cove PMIC. + + To compile this driver as a module, choose M here: the module + will be called leds-cht-wcove. + config LEDS_CPCAP tristate "LED Support for Motorola CPCAP" depends on LEDS_CLASS @@ -676,7 +700,7 @@ config LEDS_LM355x config LEDS_OT200 tristate "LED support for the Bachmann OT200" - depends on LEDS_CLASS && HAS_IOMEM && (X86_32 || COMPILE_TEST) + depends on LEDS_CLASS && HAS_IOPORT && (X86_32 || COMPILE_TEST) help This option enables support for the LEDs on the Bachmann OT200. Say Y to enable LEDs on the Bachmann OT200. @@ -807,8 +831,7 @@ config LEDS_SPI_BYTE supported: Ubiquiti airCube ISP microcontroller based LED controller. config LEDS_TI_LMU_COMMON - tristate "LED driver for TI LMU" - depends on LEDS_CLASS + tristate "LED driver for TI LMU" if COMPILE_TEST select REGMAP help Say Y to enable the LED driver for TI LMU devices. @@ -817,16 +840,16 @@ config LEDS_TI_LMU_COMMON config LEDS_LM3697 tristate "LED driver for LM3697" - depends on LEDS_TI_LMU_COMMON - depends on I2C && OF + depends on LEDS_CLASS && I2C && OF + select LEDS_TI_LMU_COMMON help Say Y to enable the LM3697 LED driver for TI LMU devices. This supports the LED device LM3697. config LEDS_LM36274 tristate "LED driver for LM36274" - depends on LEDS_TI_LMU_COMMON - depends on MFD_TI_LMU + depends on LEDS_CLASS && MFD_TI_LMU + select LEDS_TI_LMU_COMMON help Say Y to enable the LM36274 LED driver for TI LMU devices. This supports the LED device LM36274. diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index c07d1512c745..d71f1226540c 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -14,12 +14,14 @@ obj-$(CONFIG_LEDS_ADP5520) += leds-adp5520.o obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o obj-$(CONFIG_LEDS_APU) += leds-apu.o obj-$(CONFIG_LEDS_ARIEL) += leds-ariel.o +obj-$(CONFIG_LEDS_AW200XX) += leds-aw200xx.o obj-$(CONFIG_LEDS_AW2013) += leds-aw2013.o obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o obj-$(CONFIG_LEDS_BD2606MVV) += leds-bd2606mvv.o obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o +obj-$(CONFIG_LEDS_CHT_WCOVE) += leds-cht-wcove.o obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o obj-$(CONFIG_LEDS_COBALT_QUBE) += leds-cobalt-qube.o obj-$(CONFIG_LEDS_COBALT_RAQ) += leds-cobalt-raq.o diff --git a/drivers/leds/flash/leds-aat1290.c b/drivers/leds/flash/leds-aat1290.c index 589484b22c79..f12ecb2c6580 100644 --- a/drivers/leds/flash/leds-aat1290.c +++ b/drivers/leds/flash/leds-aat1290.c @@ -425,7 +425,7 @@ static void aat1290_init_v4l2_flash_config(struct aat1290_led *led, struct led_classdev *led_cdev = &led->fled_cdev.led_cdev; struct led_flash_setting *s; - strlcpy(v4l2_sd_cfg->dev_name, led_cdev->dev->kobj.name, + strscpy(v4l2_sd_cfg->dev_name, led_cdev->dev->kobj.name, sizeof(v4l2_sd_cfg->dev_name)); s = &v4l2_sd_cfg->intensity; diff --git a/drivers/leds/flash/leds-as3645a.c b/drivers/leds/flash/leds-as3645a.c index bb2249771acb..12c2609c1137 100644 --- a/drivers/leds/flash/leds-as3645a.c +++ b/drivers/leds/flash/leds-as3645a.c @@ -651,8 +651,8 @@ static int as3645a_v4l2_setup(struct as3645a *flash) }, }; - strlcpy(cfg.dev_name, led->dev->kobj.name, sizeof(cfg.dev_name)); - strlcpy(cfgind.dev_name, flash->iled_cdev.dev->kobj.name, + strscpy(cfg.dev_name, led->dev->kobj.name, sizeof(cfg.dev_name)); + strscpy(cfgind.dev_name, flash->iled_cdev.dev->kobj.name, sizeof(cfgind.dev_name)); flash->vf = v4l2_flash_init( @@ -759,7 +759,7 @@ static struct i2c_driver as3645a_i2c_driver = { .of_match_table = as3645a_of_table, .name = AS_NAME, }, - .probe_new = as3645a_probe, + .probe = as3645a_probe, .remove = as3645a_remove, .id_table = as3645a_id_table, }; diff --git a/drivers/leds/flash/leds-lm3601x.c b/drivers/leds/flash/leds-lm3601x.c index 78730e066a73..b6c524facf49 100644 --- a/drivers/leds/flash/leds-lm3601x.c +++ b/drivers/leds/flash/leds-lm3601x.c @@ -471,7 +471,7 @@ static struct i2c_driver lm3601x_i2c_driver = { .name = "lm3601x", .of_match_table = of_lm3601x_leds_match, }, - .probe_new = lm3601x_probe, + .probe = lm3601x_probe, .remove = lm3601x_remove, .id_table = lm3601x_id, }; diff --git a/drivers/leds/flash/leds-qcom-flash.c b/drivers/leds/flash/leds-qcom-flash.c index 90a24fa25a49..b089ca1a1901 100644 --- a/drivers/leds/flash/leds-qcom-flash.c +++ b/drivers/leds/flash/leds-qcom-flash.c @@ -18,7 +18,8 @@ #define FLASH_TYPE_VAL 0x18 #define FLASH_SUBTYPE_REG 0x05 -#define FLASH_SUBTYPE_3CH_VAL 0x04 +#define FLASH_SUBTYPE_3CH_PM8150_VAL 0x04 +#define FLASH_SUBTYPE_3CH_PMI8998_VAL 0x03 #define FLASH_SUBTYPE_4CH_VAL 0x07 #define FLASH_STS_3CH_OTST1 BIT(0) @@ -416,6 +417,14 @@ static int qcom_flash_led_brightness_set(struct led_classdev *led_cdev, bool enable = !!brightness; int rc; + rc = set_flash_strobe(led, SW_STROBE, false); + if (rc) + return rc; + + rc = set_flash_module_en(led, false); + if (rc) + return rc; + rc = set_flash_current(led, current_ma, TORCH_MODE); if (rc) return rc; @@ -529,9 +538,9 @@ static int qcom_flash_register_led_device(struct device *dev, struct led_init_data init_data; struct led_classdev_flash *flash = &led->flash; struct led_flash_setting *brightness, *timeout; - u32 count, current_ua, timeout_us; + u32 current_ua, timeout_us; u32 channels[4]; - int i, rc; + int i, rc, count; count = fwnode_property_count_u32(node, "led-sources"); if (count <= 0) { @@ -682,7 +691,7 @@ static int qcom_flash_led_probe(struct platform_device *pdev) return rc; } - if (val == FLASH_SUBTYPE_3CH_VAL) { + if (val == FLASH_SUBTYPE_3CH_PM8150_VAL || val == FLASH_SUBTYPE_3CH_PMI8998_VAL) { flash_data->hw_type = QCOM_MVFLASH_3CH; flash_data->max_channels = 3; regs = mvflash_3ch_regs; diff --git a/drivers/leds/flash/leds-rt4505.c b/drivers/leds/flash/leds-rt4505.c index e404fe8b0314..1ae5b387f4a5 100644 --- a/drivers/leds/flash/leds-rt4505.c +++ b/drivers/leds/flash/leds-rt4505.c @@ -419,7 +419,7 @@ static struct i2c_driver rt4505_driver = { .name = "rt4505", .of_match_table = of_match_ptr(rt4505_leds_match), }, - .probe_new = rt4505_probe, + .probe = rt4505_probe, .remove = rt4505_remove, .shutdown = rt4505_shutdown, }; diff --git a/drivers/leds/flash/leds-sgm3140.c b/drivers/leds/flash/leds-sgm3140.c index d3a30ad94ac4..d3f50dca5136 100644 --- a/drivers/leds/flash/leds-sgm3140.c +++ b/drivers/leds/flash/leds-sgm3140.c @@ -291,6 +291,7 @@ static int sgm3140_remove(struct platform_device *pdev) static const struct of_device_id sgm3140_dt_match[] = { { .compatible = "ocs,ocp8110" }, + { .compatible = "richtek,rt5033-led" }, { .compatible = "sgmicro,sgm3140" }, { /* sentinel */ } }; diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index 9255bc11f99d..6dae56b914fe 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -409,7 +409,7 @@ static int led_classdev_next_name(const char *init_name, char *name, int ret = 0; struct device *dev; - strlcpy(name, init_name, len); + strscpy(name, init_name, len); while ((ret < len) && (dev = class_find_device_by_name(leds_class, name))) { diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c index 4a97cb745788..b9b1295833c9 100644 --- a/drivers/leds/led-core.c +++ b/drivers/leds/led-core.c @@ -114,21 +114,14 @@ static void led_timer_function(struct timer_list *t) mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay)); } -static void set_brightness_delayed(struct work_struct *ws) +static void set_brightness_delayed_set_brightness(struct led_classdev *led_cdev, + unsigned int value) { - struct led_classdev *led_cdev = - container_of(ws, struct led_classdev, set_brightness_work); int ret = 0; - if (test_and_clear_bit(LED_BLINK_DISABLE, &led_cdev->work_flags)) { - led_cdev->delayed_set_value = LED_OFF; - led_stop_software_blink(led_cdev); - } - - ret = __led_set_brightness(led_cdev, led_cdev->delayed_set_value); + ret = __led_set_brightness(led_cdev, value); if (ret == -ENOTSUPP) - ret = __led_set_brightness_blocking(led_cdev, - led_cdev->delayed_set_value); + ret = __led_set_brightness_blocking(led_cdev, value); if (ret < 0 && /* LED HW might have been unplugged, therefore don't warn */ !(ret == -ENODEV && (led_cdev->flags & LED_UNREGISTERING) && @@ -137,6 +130,37 @@ static void set_brightness_delayed(struct work_struct *ws) "Setting an LED's brightness failed (%d)\n", ret); } +static void set_brightness_delayed(struct work_struct *ws) +{ + struct led_classdev *led_cdev = + container_of(ws, struct led_classdev, set_brightness_work); + + if (test_and_clear_bit(LED_BLINK_DISABLE, &led_cdev->work_flags)) { + led_stop_software_blink(led_cdev); + set_bit(LED_SET_BRIGHTNESS_OFF, &led_cdev->work_flags); + } + + /* + * Triggers may call led_set_brightness(LED_OFF), + * led_set_brightness(LED_FULL) in quick succession to disable blinking + * and turn the LED on. Both actions may have been scheduled to run + * before this work item runs once. To make sure this works properly + * handle LED_SET_BRIGHTNESS_OFF first. + */ + if (test_and_clear_bit(LED_SET_BRIGHTNESS_OFF, &led_cdev->work_flags)) + set_brightness_delayed_set_brightness(led_cdev, LED_OFF); + + if (test_and_clear_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags)) + set_brightness_delayed_set_brightness(led_cdev, led_cdev->delayed_set_value); + + if (test_and_clear_bit(LED_SET_BLINK, &led_cdev->work_flags)) { + unsigned long delay_on = led_cdev->delayed_delay_on; + unsigned long delay_off = led_cdev->delayed_delay_off; + + led_blink_set(led_cdev, &delay_on, &delay_off); + } +} + static void led_set_software_blink(struct led_classdev *led_cdev, unsigned long delay_on, unsigned long delay_off) @@ -229,6 +253,22 @@ void led_blink_set_oneshot(struct led_classdev *led_cdev, } EXPORT_SYMBOL_GPL(led_blink_set_oneshot); +void led_blink_set_nosleep(struct led_classdev *led_cdev, unsigned long delay_on, + unsigned long delay_off) +{ + /* If necessary delegate to a work queue task. */ + if (led_cdev->blink_set && led_cdev->brightness_set_blocking) { + led_cdev->delayed_delay_on = delay_on; + led_cdev->delayed_delay_off = delay_off; + set_bit(LED_SET_BLINK, &led_cdev->work_flags); + schedule_work(&led_cdev->set_brightness_work); + return; + } + + led_blink_set(led_cdev, &delay_on, &delay_off); +} +EXPORT_SYMBOL_GPL(led_blink_set_nosleep); + void led_stop_software_blink(struct led_classdev *led_cdev) { del_timer_sync(&led_cdev->blink_timer); @@ -271,8 +311,23 @@ void led_set_brightness_nopm(struct led_classdev *led_cdev, unsigned int value) if (!__led_set_brightness(led_cdev, value)) return; - /* If brightness setting can sleep, delegate it to a work queue task */ - led_cdev->delayed_set_value = value; + /* + * Brightness setting can sleep, delegate it to a work queue task. + * value 0 / LED_OFF is special, since it also disables hw-blinking + * (sw-blink disable is handled in led_set_brightness()). + * To avoid a hw-blink-disable getting lost when a second brightness + * change is done immediately afterwards (before the work runs), + * it uses a separate work_flag. + */ + if (value) { + led_cdev->delayed_set_value = value; + set_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags); + } else { + clear_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags); + clear_bit(LED_SET_BLINK, &led_cdev->work_flags); + set_bit(LED_SET_BRIGHTNESS_OFF, &led_cdev->work_flags); + } + schedule_work(&led_cdev->set_brightness_work); } EXPORT_SYMBOL_GPL(led_set_brightness_nopm); diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index 072491d3e17b..6a5e1f41f9a4 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -185,6 +185,7 @@ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig) led_cdev->trigger = NULL; led_cdev->trigger_data = NULL; led_cdev->activated = false; + led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER; led_set_brightness(led_cdev, LED_OFF); } if (trig) { @@ -393,8 +394,8 @@ void led_trigger_event(struct led_trigger *trig, EXPORT_SYMBOL_GPL(led_trigger_event); static void led_trigger_blink_setup(struct led_trigger *trig, - unsigned long *delay_on, - unsigned long *delay_off, + unsigned long delay_on, + unsigned long delay_off, int oneshot, int invert) { @@ -406,25 +407,25 @@ static void led_trigger_blink_setup(struct led_trigger *trig, rcu_read_lock(); list_for_each_entry_rcu(led_cdev, &trig->led_cdevs, trig_list) { if (oneshot) - led_blink_set_oneshot(led_cdev, delay_on, delay_off, + led_blink_set_oneshot(led_cdev, &delay_on, &delay_off, invert); else - led_blink_set(led_cdev, delay_on, delay_off); + led_blink_set_nosleep(led_cdev, delay_on, delay_off); } rcu_read_unlock(); } void led_trigger_blink(struct led_trigger *trig, - unsigned long *delay_on, - unsigned long *delay_off) + unsigned long delay_on, + unsigned long delay_off) { led_trigger_blink_setup(trig, delay_on, delay_off, 0, 0); } EXPORT_SYMBOL_GPL(led_trigger_blink); void led_trigger_blink_oneshot(struct led_trigger *trig, - unsigned long *delay_on, - unsigned long *delay_off, + unsigned long delay_on, + unsigned long delay_off, int invert) { led_trigger_blink_setup(trig, delay_on, delay_off, 1, invert); diff --git a/drivers/leds/leds-an30259a.c b/drivers/leds/leds-an30259a.c index 89df267853a9..24b1041213c2 100644 --- a/drivers/leds/leds-an30259a.c +++ b/drivers/leds/leds-an30259a.c @@ -346,7 +346,7 @@ static struct i2c_driver an30259a_driver = { .name = "leds-an30259a", .of_match_table = of_match_ptr(an30259a_match_table), }, - .probe_new = an30259a_probe, + .probe = an30259a_probe, .remove = an30259a_remove, .id_table = an30259a_id, }; diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c new file mode 100644 index 000000000000..96979b8e09b7 --- /dev/null +++ b/drivers/leds/leds-aw200xx.c @@ -0,0 +1,594 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Awinic AW20036/AW20054/AW20072 LED driver + * + * Copyright (c) 2023, SberDevices. All Rights Reserved. + * + * Author: Martin Kurbanov <mmkurbanov@sberdevices.ru> + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/container_of.h> +#include <linux/i2c.h> +#include <linux/leds.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/time.h> +#include <linux/units.h> + +#define AW200XX_DIM_MAX (BIT(6) - 1) +#define AW200XX_FADE_MAX (BIT(8) - 1) +#define AW200XX_IMAX_DEFAULT_uA 60000 +#define AW200XX_IMAX_MAX_uA 160000 +#define AW200XX_IMAX_MIN_uA 3300 + +/* Page 0 */ +#define AW200XX_REG_PAGE0_BASE 0xc000 + +/* Select page register */ +#define AW200XX_REG_PAGE 0xF0 +#define AW200XX_PAGE_MASK (GENMASK(7, 6) | GENMASK(2, 0)) +#define AW200XX_PAGE_SHIFT 0 +#define AW200XX_NUM_PAGES 6 +#define AW200XX_PAGE_SIZE 256 +#define AW200XX_REG(page, reg) \ + (AW200XX_REG_PAGE0_BASE + (page) * AW200XX_PAGE_SIZE + (reg)) +#define AW200XX_REG_MAX \ + AW200XX_REG(AW200XX_NUM_PAGES - 1, AW200XX_PAGE_SIZE - 1) +#define AW200XX_PAGE0 0 +#define AW200XX_PAGE1 1 +#define AW200XX_PAGE2 2 +#define AW200XX_PAGE3 3 +#define AW200XX_PAGE4 4 +#define AW200XX_PAGE5 5 + +/* Chip ID register */ +#define AW200XX_REG_IDR AW200XX_REG(AW200XX_PAGE0, 0x00) +#define AW200XX_IDR_CHIPID 0x18 + +/* Sleep mode register */ +#define AW200XX_REG_SLPCR AW200XX_REG(AW200XX_PAGE0, 0x01) +#define AW200XX_SLPCR_ACTIVE 0x00 + +/* Reset register */ +#define AW200XX_REG_RSTR AW200XX_REG(AW200XX_PAGE0, 0x02) +#define AW200XX_RSTR_RESET 0x01 + +/* Global current configuration register */ +#define AW200XX_REG_GCCR AW200XX_REG(AW200XX_PAGE0, 0x03) +#define AW200XX_GCCR_IMAX_MASK GENMASK(7, 4) +#define AW200XX_GCCR_IMAX(x) ((x) << 4) +#define AW200XX_GCCR_ALLON BIT(3) + +/* Fast clear display control register */ +#define AW200XX_REG_FCD AW200XX_REG(AW200XX_PAGE0, 0x04) +#define AW200XX_FCD_CLEAR 0x01 + +/* Display size configuration */ +#define AW200XX_REG_DSIZE AW200XX_REG(AW200XX_PAGE0, 0x80) +#define AW200XX_DSIZE_COLUMNS_MAX 12 + +#define AW200XX_LED2REG(x, columns) \ + ((x) + (((x) / (columns)) * (AW200XX_DSIZE_COLUMNS_MAX - (columns)))) + +/* + * DIM current configuration register (page 4). + * The even address for current DIM configuration. + * The odd address for current FADE configuration + */ +#define AW200XX_REG_DIM(x, columns) \ + AW200XX_REG(AW200XX_PAGE4, AW200XX_LED2REG(x, columns) * 2) +#define AW200XX_REG_DIM2FADE(x) ((x) + 1) + +/* + * Duty ratio of display scan (see p.15 of datasheet for formula): + * duty = (592us / 600.5us) * (1 / (display_rows + 1)) + * + * Multiply to 1000 (MILLI) to improve the accuracy of calculations. + */ +#define AW200XX_DUTY_RATIO(rows) \ + (((592UL * USEC_PER_SEC) / 600500UL) * (MILLI / (rows)) / MILLI) + +struct aw200xx_chipdef { + u32 channels; + u32 display_size_rows_max; + u32 display_size_columns; +}; + +struct aw200xx_led { + struct led_classdev cdev; + struct aw200xx *chip; + int dim; + u32 num; +}; + +struct aw200xx { + const struct aw200xx_chipdef *cdef; + struct i2c_client *client; + struct regmap *regmap; + struct mutex mutex; + u32 num_leds; + u32 display_rows; + struct aw200xx_led leds[]; +}; + +static ssize_t dim_show(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev); + int dim = led->dim; + + if (dim < 0) + return sysfs_emit(buf, "auto\n"); + + return sysfs_emit(buf, "%d\n", dim); +} + +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; + + if (sysfs_streq(buf, "auto")) { + dim = -1; + } else { + ret = kstrtoint(buf, 0, &dim); + if (ret) + return ret; + + if (dim > AW200XX_DIM_MAX) + return -EINVAL; + } + + mutex_lock(&chip->mutex); + + if (dim >= 0) { + ret = regmap_write(chip->regmap, + AW200XX_REG_DIM(led->num, columns), dim); + if (ret) + goto out_unlock; + } + + led->dim = dim; + ret = count; + +out_unlock: + mutex_unlock(&chip->mutex); + return ret; +} +static DEVICE_ATTR_RW(dim); + +static struct attribute *dim_attrs[] = { + &dev_attr_dim.attr, + NULL +}; +ATTRIBUTE_GROUPS(dim); + +static int aw200xx_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev); + struct aw200xx *chip = led->chip; + int dim; + u32 reg; + int ret; + + mutex_lock(&chip->mutex); + + reg = AW200XX_REG_DIM(led->num, chip->cdef->display_size_columns); + + dim = led->dim; + if (dim < 0) + dim = max_t(int, + brightness / (AW200XX_FADE_MAX / AW200XX_DIM_MAX), + 1); + + ret = regmap_write(chip->regmap, reg, dim); + if (ret) + goto out_unlock; + + ret = regmap_write(chip->regmap, + AW200XX_REG_DIM2FADE(reg), brightness); + +out_unlock: + mutex_unlock(&chip->mutex); + + return ret; +} + +static u32 aw200xx_imax_from_global(const struct aw200xx *const chip, + u32 global_imax_uA) +{ + u64 led_imax_uA; + + /* + * The output current of each LED (see p.14 of datasheet for formula): + * Iled = Imax * (dim / 63) * ((fade + 1) / 256) * duty + * + * The value of duty is determined by the following formula: + * duty = (592us / 600.5us) * (1 / (display_rows + 1)) + * + * Calculated for the maximum values of fade and dim. + * We divide by 1000 because we earlier multiplied by 1000 to improve + * accuracy when calculating the duty. + */ + led_imax_uA = global_imax_uA * AW200XX_DUTY_RATIO(chip->display_rows); + do_div(led_imax_uA, MILLI); + + return led_imax_uA; +} + +static u32 aw200xx_imax_to_global(const struct aw200xx *const chip, + u32 led_imax_uA) +{ + u32 duty = AW200XX_DUTY_RATIO(chip->display_rows); + + /* The output current of each LED (see p.14 of datasheet for formula) */ + return (led_imax_uA * 1000U) / duty; +} + +#define AW200XX_IMAX_MULTIPLIER1 10000 +#define AW200XX_IMAX_MULTIPLIER2 3333 +#define AW200XX_IMAX_BASE_VAL1 0 +#define AW200XX_IMAX_BASE_VAL2 8 + +/* + * The AW200XX has a 4-bit register (GCCR) to configure the global current, + * which ranges from 3.3mA to 160mA. The following table indicates the values + * of the global current, divided into two parts: + * + * +-----------+-----------------+-----------+-----------------+ + * | reg value | global max (mA) | reg value | global max (mA) | + * +-----------+-----------------+-----------+-----------------+ + * | 0 | 10 | 8 | 3.3 | + * | 1 | 20 | 9 | 6.7 | + * | 2 | 30 | 10 | 10 | + * | 3 | 40 | 11 | 13.3 | + * | 4 | 60 | 12 | 20 | + * | 5 | 80 | 13 | 26.7 | + * | 6 | 120 | 14 | 40 | + * | 7 | 160 | 15 | 53.3 | + * +-----------+-----------------+-----------+-----------------+ + * + * The left part with a multiplier of 10, and the right part with a multiplier + * of 3.3. + * So we have two formulas to calculate the global current: + * for the left part of the table: + * imax = coefficient * 10 + * + * for the right part of the table: + * imax = coefficient * 3.3 + * + * The coefficient table consists of the following values: + * 1, 2, 3, 4, 6, 8, 12, 16. + */ +static int aw200xx_set_imax(const struct aw200xx *const chip, + u32 led_imax_uA) +{ + u32 g_imax_uA = aw200xx_imax_to_global(chip, led_imax_uA); + u32 coeff_table[] = {1, 2, 3, 4, 6, 8, 12, 16}; + u32 gccr_imax = UINT_MAX; + u32 cur_imax = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_table); i++) { + u32 imax; + + /* select closest ones */ + imax = coeff_table[i] * AW200XX_IMAX_MULTIPLIER1; + if (g_imax_uA >= imax && imax > cur_imax) { + cur_imax = imax; + gccr_imax = i + AW200XX_IMAX_BASE_VAL1; + } + + imax = coeff_table[i] * AW200XX_IMAX_MULTIPLIER2; + imax = DIV_ROUND_CLOSEST(imax, 100) * 100; + if (g_imax_uA >= imax && imax > cur_imax) { + cur_imax = imax; + gccr_imax = i + AW200XX_IMAX_BASE_VAL2; + } + } + + if (gccr_imax == UINT_MAX) + return -EINVAL; + + return regmap_update_bits(chip->regmap, AW200XX_REG_GCCR, + AW200XX_GCCR_IMAX_MASK, + AW200XX_GCCR_IMAX(gccr_imax)); +} + +static int aw200xx_chip_reset(const struct aw200xx *const chip) +{ + int ret; + + ret = regmap_write(chip->regmap, AW200XX_REG_RSTR, AW200XX_RSTR_RESET); + if (ret) + return ret; + + regcache_mark_dirty(chip->regmap); + return regmap_write(chip->regmap, AW200XX_REG_FCD, AW200XX_FCD_CLEAR); +} + +static int aw200xx_chip_init(const struct aw200xx *const chip) +{ + int ret; + + ret = regmap_write(chip->regmap, AW200XX_REG_DSIZE, + chip->display_rows - 1); + if (ret) + return ret; + + ret = regmap_write(chip->regmap, AW200XX_REG_SLPCR, + AW200XX_SLPCR_ACTIVE); + if (ret) + return ret; + + return regmap_update_bits(chip->regmap, AW200XX_REG_GCCR, + AW200XX_GCCR_ALLON, AW200XX_GCCR_ALLON); +} + +static int aw200xx_chip_check(const struct aw200xx *const chip) +{ + struct device *dev = &chip->client->dev; + u32 chipid; + int ret; + + ret = regmap_read(chip->regmap, AW200XX_REG_IDR, &chipid); + if (ret) + return dev_err_probe(dev, ret, "Failed to read chip ID\n"); + + if (chipid != AW200XX_IDR_CHIPID) + return dev_err_probe(dev, -ENODEV, + "Chip reported wrong ID: %x\n", chipid); + + return 0; +} + +static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip) +{ + struct fwnode_handle *child; + u32 current_min, current_max, min_uA; + int ret; + int i; + + ret = device_property_read_u32(dev, "awinic,display-rows", + &chip->display_rows); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read 'display-rows' property\n"); + + if (!chip->display_rows || + chip->display_rows > chip->cdef->display_size_rows_max) { + return dev_err_probe(dev, ret, + "Invalid leds display size %u\n", + chip->display_rows); + } + + 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(dev, child) { + struct led_init_data init_data = {}; + struct aw200xx_led *led; + u32 source, imax; + + ret = fwnode_property_read_u32(child, "reg", &source); + if (ret) { + dev_err(dev, "Missing reg property\n"); + chip->num_leds--; + continue; + } + + if (source >= chip->cdef->channels) { + dev_err(dev, "LED reg %u out of range (max %u)\n", + source, chip->cdef->channels); + chip->num_leds--; + continue; + } + + ret = fwnode_property_read_u32(child, "led-max-microamp", + &imax); + if (ret) { + dev_info(&chip->client->dev, + "DT property led-max-microamp is missing\n"); + } else if (imax < current_min || imax > current_max) { + dev_err(dev, "Invalid value %u for led-max-microamp\n", + imax); + chip->num_leds--; + continue; + } else { + min_uA = min(min_uA, imax); + } + + led = &chip->leds[i]; + led->dim = -1; + led->num = source; + led->chip = chip; + led->cdev.brightness_set_blocking = aw200xx_brightness_set; + led->cdev.groups = dim_groups; + init_data.fwnode = child; + + ret = devm_led_classdev_register_ext(dev, &led->cdev, + &init_data); + if (ret) { + fwnode_handle_put(child); + break; + } + + i++; + } + + if (!chip->num_leds) + return -EINVAL; + + if (min_uA == UINT_MAX) { + min_uA = aw200xx_imax_from_global(chip, + AW200XX_IMAX_DEFAULT_uA); + } + + return aw200xx_set_imax(chip, min_uA); +} + +static const struct regmap_range_cfg aw200xx_ranges[] = { + { + .name = "aw200xx", + .range_min = 0, + .range_max = AW200XX_REG_MAX, + .selector_reg = AW200XX_REG_PAGE, + .selector_mask = AW200XX_PAGE_MASK, + .selector_shift = AW200XX_PAGE_SHIFT, + .window_start = 0, + .window_len = AW200XX_PAGE_SIZE, + }, +}; + +static const struct regmap_range aw200xx_writeonly_ranges[] = { + regmap_reg_range(AW200XX_REG(AW200XX_PAGE1, 0x00), AW200XX_REG_MAX), +}; + +static const struct regmap_access_table aw200xx_readable_table = { + .no_ranges = aw200xx_writeonly_ranges, + .n_no_ranges = ARRAY_SIZE(aw200xx_writeonly_ranges), +}; + +static const struct regmap_range aw200xx_readonly_ranges[] = { + regmap_reg_range(AW200XX_REG_IDR, AW200XX_REG_IDR), +}; + +static const struct regmap_access_table aw200xx_writeable_table = { + .no_ranges = aw200xx_readonly_ranges, + .n_no_ranges = ARRAY_SIZE(aw200xx_readonly_ranges), +}; + +static const struct regmap_config aw200xx_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AW200XX_REG_MAX, + .ranges = aw200xx_ranges, + .num_ranges = ARRAY_SIZE(aw200xx_ranges), + .rd_table = &aw200xx_readable_table, + .wr_table = &aw200xx_writeable_table, + .cache_type = REGCACHE_RBTREE, +}; + +static int aw200xx_probe(struct i2c_client *client) +{ + const struct aw200xx_chipdef *cdef; + struct aw200xx *chip; + int count; + int ret; + + cdef = device_get_match_data(&client->dev); + if (!cdef) + return -ENODEV; + + count = device_get_child_node_count(&client->dev); + if (!count || count > cdef->channels) + return dev_err_probe(&client->dev, -EINVAL, + "Incorrect number of leds (%d)", count); + + chip = devm_kzalloc(&client->dev, struct_size(chip, leds, count), + GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->cdef = cdef; + chip->num_leds = count; + chip->client = client; + i2c_set_clientdata(client, chip); + + chip->regmap = devm_regmap_init_i2c(client, &aw200xx_regmap_config); + if (IS_ERR(chip->regmap)) + return PTR_ERR(chip->regmap); + + ret = aw200xx_chip_check(chip); + if (ret) + return ret; + + mutex_init(&chip->mutex); + + /* 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; + + ret = aw200xx_probe_fw(&client->dev, chip); + if (ret) + goto out_unlock; + + ret = aw200xx_chip_init(chip); + +out_unlock: + mutex_unlock(&chip->mutex); + return ret; +} + +static void aw200xx_remove(struct i2c_client *client) +{ + struct aw200xx *chip = i2c_get_clientdata(client); + + aw200xx_chip_reset(chip); + mutex_destroy(&chip->mutex); +} + +static const struct aw200xx_chipdef aw20036_cdef = { + .channels = 36, + .display_size_rows_max = 3, + .display_size_columns = 12, +}; + +static const struct aw200xx_chipdef aw20054_cdef = { + .channels = 54, + .display_size_rows_max = 6, + .display_size_columns = 9, +}; + +static const struct aw200xx_chipdef aw20072_cdef = { + .channels = 72, + .display_size_rows_max = 6, + .display_size_columns = 12, +}; + +static const struct i2c_device_id aw200xx_id[] = { + { "aw20036" }, + { "aw20054" }, + { "aw20072" }, + {} +}; +MODULE_DEVICE_TABLE(i2c, aw200xx_id); + +static const struct of_device_id aw200xx_match_table[] = { + { .compatible = "awinic,aw20036", .data = &aw20036_cdef, }, + { .compatible = "awinic,aw20054", .data = &aw20054_cdef, }, + { .compatible = "awinic,aw20072", .data = &aw20072_cdef, }, + {} +}; +MODULE_DEVICE_TABLE(of, aw200xx_match_table); + +static struct i2c_driver aw200xx_driver = { + .driver = { + .name = "aw200xx", + .of_match_table = aw200xx_match_table, + }, + .probe_new = aw200xx_probe, + .remove = aw200xx_remove, + .id_table = aw200xx_id, +}; +module_i2c_driver(aw200xx_driver); + +MODULE_AUTHOR("Martin Kurbanov <mmkurbanov@sberdevices.ru>"); +MODULE_DESCRIPTION("AW200XX LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-aw2013.c b/drivers/leds/leds-aw2013.c index 0b52fc9097c6..59765640b70f 100644 --- a/drivers/leds/leds-aw2013.c +++ b/drivers/leds/leds-aw2013.c @@ -422,7 +422,7 @@ static struct i2c_driver aw2013_driver = { .name = "leds-aw2013", .of_match_table = of_match_ptr(aw2013_match_table), }, - .probe_new = aw2013_probe, + .probe = aw2013_probe, .remove = aw2013_remove, }; diff --git a/drivers/leds/leds-bd2606mvv.c b/drivers/leds/leds-bd2606mvv.c index 76f9d4d70f9a..3fda712d2f80 100644 --- a/drivers/leds/leds-bd2606mvv.c +++ b/drivers/leds/leds-bd2606mvv.c @@ -150,7 +150,7 @@ static struct i2c_driver bd2606mvv_driver = { .name = "leds-bd2606mvv", .of_match_table = of_match_ptr(of_bd2606mvv_leds_match), }, - .probe_new = bd2606mvv_probe, + .probe = bd2606mvv_probe, }; module_i2c_driver(bd2606mvv_driver); diff --git a/drivers/leds/leds-bd2802.c b/drivers/leds/leds-bd2802.c index 601185ddabcc..0792ea126cea 100644 --- a/drivers/leds/leds-bd2802.c +++ b/drivers/leds/leds-bd2802.c @@ -786,7 +786,7 @@ static struct i2c_driver bd2802_i2c_driver = { .name = "BD2802", .pm = &bd2802_pm, }, - .probe_new = bd2802_probe, + .probe = bd2802_probe, .remove = bd2802_remove, .id_table = bd2802_id, }; diff --git a/drivers/leds/leds-blinkm.c b/drivers/leds/leds-blinkm.c index 37f2f32ae42d..2782da1a1930 100644 --- a/drivers/leds/leds-blinkm.c +++ b/drivers/leds/leds-blinkm.c @@ -561,7 +561,7 @@ static int blinkm_detect(struct i2c_client *client, struct i2c_board_info *info) return -ENODEV; } - strlcpy(info->type, "blinkm", I2C_NAME_SIZE); + strscpy(info->type, "blinkm", I2C_NAME_SIZE); return 0; } @@ -730,7 +730,7 @@ static struct i2c_driver blinkm_driver = { .driver = { .name = "blinkm", }, - .probe_new = blinkm_probe, + .probe = blinkm_probe, .remove = blinkm_remove, .id_table = blinkm_id, .detect = blinkm_detect, diff --git a/drivers/leds/leds-cht-wcove.c b/drivers/leds/leds-cht-wcove.c new file mode 100644 index 000000000000..b4998402b8c6 --- /dev/null +++ b/drivers/leds/leds-cht-wcove.c @@ -0,0 +1,476 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for LEDs connected to the Intel Cherry Trail Whiskey Cove PMIC + * + * Copyright 2019 Yauhen Kharuzhy <jekhor@gmail.com> + * Copyright 2023 Hans de Goede <hansg@kernel.org> + * + * Register info comes from the Lenovo Yoga Book Android opensource code + * available from Lenovo. File lenovo_yb1_x90f_l_osc_201803.7z path in the 7z: + * YB1_source_code/kernel/cht/drivers/misc/charger_gp_led.c + */ + +#include <linux/kernel.h> +#include <linux/leds.h> +#include <linux/mfd/intel_soc_pmic.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/suspend.h> + +#define CHT_WC_LED1_CTRL 0x5e1f +#define CHT_WC_LED1_FSM 0x5e20 +#define CHT_WC_LED1_PWM 0x5e21 + +#define CHT_WC_LED2_CTRL 0x4fdf +#define CHT_WC_LED2_FSM 0x4fe0 +#define CHT_WC_LED2_PWM 0x4fe1 + +#define CHT_WC_LED1_SWCTL BIT(0) /* HW or SW control of charging led */ +#define CHT_WC_LED1_ON BIT(1) + +#define CHT_WC_LED2_ON BIT(0) +#define CHT_WC_LED_I_MA2_5 (2 << 2) /* LED current limit */ +#define CHT_WC_LED_I_MASK GENMASK(3, 2) /* LED current limit mask */ + +#define CHT_WC_LED_F_1_4_HZ (0 << 4) +#define CHT_WC_LED_F_1_2_HZ (1 << 4) +#define CHT_WC_LED_F_1_HZ (2 << 4) +#define CHT_WC_LED_F_2_HZ (3 << 4) +#define CHT_WC_LED_F_MASK GENMASK(5, 4) + +#define CHT_WC_LED_EFF_OFF (0 << 1) +#define CHT_WC_LED_EFF_ON (1 << 1) +#define CHT_WC_LED_EFF_BLINKING (2 << 1) +#define CHT_WC_LED_EFF_BREATHING (3 << 1) +#define CHT_WC_LED_EFF_MASK GENMASK(2, 1) + +#define CHT_WC_LED_COUNT 2 + +struct cht_wc_led_regs { + /* Register addresses */ + u16 ctrl; + u16 fsm; + u16 pwm; + /* Mask + values for turning the LED on/off */ + u8 on_off_mask; + u8 on_val; + u8 off_val; +}; + +struct cht_wc_led_saved_regs { + unsigned int ctrl; + unsigned int fsm; + unsigned int pwm; +}; + +struct cht_wc_led { + struct led_classdev cdev; + const struct cht_wc_led_regs *regs; + struct regmap *regmap; + struct mutex mutex; + struct cht_wc_led_saved_regs saved_regs; +}; + +struct cht_wc_leds { + struct cht_wc_led leds[CHT_WC_LED_COUNT]; + /* Saved LED1 initial register values */ + struct cht_wc_led_saved_regs led1_initial_regs; +}; + +static const struct cht_wc_led_regs cht_wc_led_regs[CHT_WC_LED_COUNT] = { + { + .ctrl = CHT_WC_LED1_CTRL, + .fsm = CHT_WC_LED1_FSM, + .pwm = CHT_WC_LED1_PWM, + .on_off_mask = CHT_WC_LED1_SWCTL | CHT_WC_LED1_ON, + .on_val = CHT_WC_LED1_SWCTL | CHT_WC_LED1_ON, + .off_val = CHT_WC_LED1_SWCTL, + }, + { + .ctrl = CHT_WC_LED2_CTRL, + .fsm = CHT_WC_LED2_FSM, + .pwm = CHT_WC_LED2_PWM, + .on_off_mask = CHT_WC_LED2_ON, + .on_val = CHT_WC_LED2_ON, + .off_val = 0, + }, +}; + +static const char * const cht_wc_leds_names[CHT_WC_LED_COUNT] = { + "platform::" LED_FUNCTION_CHARGING, + "platform::" LED_FUNCTION_INDICATOR, +}; + +static int cht_wc_leds_brightness_set(struct led_classdev *cdev, + enum led_brightness value) +{ + struct cht_wc_led *led = container_of(cdev, struct cht_wc_led, cdev); + int ret; + + mutex_lock(&led->mutex); + + if (!value) { + ret = regmap_update_bits(led->regmap, led->regs->ctrl, + led->regs->on_off_mask, led->regs->off_val); + if (ret < 0) { + dev_err(cdev->dev, "Failed to turn off: %d\n", ret); + goto out; + } + + /* Disable HW blinking */ + ret = regmap_update_bits(led->regmap, led->regs->fsm, + CHT_WC_LED_EFF_MASK, CHT_WC_LED_EFF_ON); + if (ret < 0) + dev_err(cdev->dev, "Failed to update LED FSM reg: %d\n", ret); + } else { + ret = regmap_write(led->regmap, led->regs->pwm, value); + if (ret < 0) { + dev_err(cdev->dev, "Failed to set brightness: %d\n", ret); + goto out; + } + + ret = regmap_update_bits(led->regmap, led->regs->ctrl, + led->regs->on_off_mask, led->regs->on_val); + if (ret < 0) + dev_err(cdev->dev, "Failed to turn on: %d\n", ret); + } +out: + mutex_unlock(&led->mutex); + return ret; +} + +static enum led_brightness cht_wc_leds_brightness_get(struct led_classdev *cdev) +{ + struct cht_wc_led *led = container_of(cdev, struct cht_wc_led, cdev); + unsigned int val; + int ret; + + mutex_lock(&led->mutex); + + ret = regmap_read(led->regmap, led->regs->ctrl, &val); + if (ret < 0) { + dev_err(cdev->dev, "Failed to read LED CTRL reg: %d\n", ret); + ret = 0; + goto done; + } + + val &= led->regs->on_off_mask; + if (val != led->regs->on_val) { + ret = 0; + goto done; + } + + ret = regmap_read(led->regmap, led->regs->pwm, &val); + if (ret < 0) { + dev_err(cdev->dev, "Failed to read LED PWM reg: %d\n", ret); + ret = 0; + goto done; + } + + ret = val; +done: + mutex_unlock(&led->mutex); + + return ret; +} + +/* Return blinking period for given CTRL reg value */ +static unsigned long cht_wc_leds_get_period(int ctrl) +{ + ctrl &= CHT_WC_LED_F_MASK; + + switch (ctrl) { + case CHT_WC_LED_F_1_4_HZ: + return 1000 * 4; + case CHT_WC_LED_F_1_2_HZ: + return 1000 * 2; + case CHT_WC_LED_F_1_HZ: + return 1000; + case CHT_WC_LED_F_2_HZ: + return 1000 / 2; + } + + return 0; +} + +/* + * Find suitable hardware blink mode for given period. + * period < 750 ms - select 2 HZ + * 750 ms <= period < 1500 ms - select 1 HZ + * 1500 ms <= period < 3000 ms - select 1/2 HZ + * 3000 ms <= period < 5000 ms - select 1/4 HZ + * 5000 ms <= period - return -1 + */ +static int cht_wc_leds_find_freq(unsigned long period) +{ + if (period < 750) + return CHT_WC_LED_F_2_HZ; + else if (period < 1500) + return CHT_WC_LED_F_1_HZ; + else if (period < 3000) + return CHT_WC_LED_F_1_2_HZ; + else if (period < 5000) + return CHT_WC_LED_F_1_4_HZ; + else + return -1; +} + +static int cht_wc_leds_set_effect(struct led_classdev *cdev, + unsigned long *delay_on, + unsigned long *delay_off, + u8 effect) +{ + struct cht_wc_led *led = container_of(cdev, struct cht_wc_led, cdev); + int ctrl, ret; + + mutex_lock(&led->mutex); + + /* Blink with 1 Hz as default if nothing specified */ + if (!*delay_on && !*delay_off) + *delay_on = *delay_off = 500; + + ctrl = cht_wc_leds_find_freq(*delay_on + *delay_off); + if (ctrl < 0) { + /* Disable HW blinking */ + ret = regmap_update_bits(led->regmap, led->regs->fsm, + CHT_WC_LED_EFF_MASK, CHT_WC_LED_EFF_ON); + if (ret < 0) + dev_err(cdev->dev, "Failed to update LED FSM reg: %d\n", ret); + + /* Fallback to software timer */ + *delay_on = *delay_off = 0; + ret = -EINVAL; + goto done; + } + + ret = regmap_update_bits(led->regmap, led->regs->fsm, + CHT_WC_LED_EFF_MASK, effect); + if (ret < 0) + dev_err(cdev->dev, "Failed to update LED FSM reg: %d\n", ret); + + /* Set the frequency and make sure the LED is on */ + ret = regmap_update_bits(led->regmap, led->regs->ctrl, + CHT_WC_LED_F_MASK | led->regs->on_off_mask, + ctrl | led->regs->on_val); + if (ret < 0) + dev_err(cdev->dev, "Failed to update LED CTRL reg: %d\n", ret); + + *delay_off = *delay_on = cht_wc_leds_get_period(ctrl) / 2; + +done: + mutex_unlock(&led->mutex); + + return ret; +} + +static int cht_wc_leds_blink_set(struct led_classdev *cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + u8 effect = CHT_WC_LED_EFF_BLINKING; + + /* + * The desired default behavior of LED1 / the charge LED is breathing + * while charging and on/solid when full. Since triggers cannot select + * breathing, blink_set() gets called when charging. Use slow breathing + * when the default "charging-blink-full-solid" trigger is used to + * achieve the desired default behavior. + */ + if (cdev->flags & LED_INIT_DEFAULT_TRIGGER) { + *delay_on = *delay_off = 1000; + effect = CHT_WC_LED_EFF_BREATHING; + } + + return cht_wc_leds_set_effect(cdev, delay_on, delay_off, effect); +} + +static int cht_wc_leds_pattern_set(struct led_classdev *cdev, + struct led_pattern *pattern, + u32 len, int repeat) +{ + unsigned long delay_off, delay_on; + + if (repeat > 0 || len != 2 || + pattern[0].brightness != 0 || pattern[1].brightness != 1 || + pattern[0].delta_t != pattern[1].delta_t || + (pattern[0].delta_t != 250 && pattern[0].delta_t != 500 && + pattern[0].delta_t != 1000 && pattern[0].delta_t != 2000)) + return -EINVAL; + + delay_off = pattern[0].delta_t; + delay_on = pattern[1].delta_t; + + return cht_wc_leds_set_effect(cdev, &delay_on, &delay_off, CHT_WC_LED_EFF_BREATHING); +} + +static int cht_wc_leds_pattern_clear(struct led_classdev *cdev) +{ + return cht_wc_leds_brightness_set(cdev, 0); +} + +static int cht_wc_led_save_regs(struct cht_wc_led *led, + struct cht_wc_led_saved_regs *saved_regs) +{ + int ret; + + ret = regmap_read(led->regmap, led->regs->ctrl, &saved_regs->ctrl); + if (ret < 0) + return ret; + + ret = regmap_read(led->regmap, led->regs->fsm, &saved_regs->fsm); + if (ret < 0) + return ret; + + return regmap_read(led->regmap, led->regs->pwm, &saved_regs->pwm); +} + +static void cht_wc_led_restore_regs(struct cht_wc_led *led, + const struct cht_wc_led_saved_regs *saved_regs) +{ + regmap_write(led->regmap, led->regs->ctrl, saved_regs->ctrl); + regmap_write(led->regmap, led->regs->fsm, saved_regs->fsm); + regmap_write(led->regmap, led->regs->pwm, saved_regs->pwm); +} + +static int cht_wc_leds_probe(struct platform_device *pdev) +{ + struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); + struct cht_wc_leds *leds; + int ret; + int i; + + /* + * On the Lenovo Yoga Tab 3 the LED1 driver output is actually + * connected to a haptic feedback motor rather then a LED. + * So do not register a LED classdev there (LED2 is unused). + */ + if (pmic->cht_wc_model == INTEL_CHT_WC_LENOVO_YT3_X90) + return -ENODEV; + + leds = devm_kzalloc(&pdev->dev, sizeof(*leds), GFP_KERNEL); + if (!leds) + return -ENOMEM; + + /* + * LED1 might be in hw-controlled mode when this driver gets loaded; and + * since the PMIC is always powered by the battery any changes made are + * permanent. Save LED1 regs to restore them on remove() or shutdown(). + */ + leds->leds[0].regs = &cht_wc_led_regs[0]; + leds->leds[0].regmap = pmic->regmap; + ret = cht_wc_led_save_regs(&leds->leds[0], &leds->led1_initial_regs); + if (ret < 0) + return ret; + + /* Set LED1 default trigger based on machine model */ + switch (pmic->cht_wc_model) { + case INTEL_CHT_WC_GPD_WIN_POCKET: + leds->leds[0].cdev.default_trigger = "max170xx_battery-charging-blink-full-solid"; + break; + case INTEL_CHT_WC_XIAOMI_MIPAD2: + leds->leds[0].cdev.default_trigger = "bq27520-0-charging-blink-full-solid"; + break; + case INTEL_CHT_WC_LENOVO_YOGABOOK1: + leds->leds[0].cdev.default_trigger = "bq27542-0-charging-blink-full-solid"; + break; + default: + dev_warn(&pdev->dev, "Unknown model, no default charging trigger\n"); + break; + } + + for (i = 0; i < CHT_WC_LED_COUNT; i++) { + struct cht_wc_led *led = &leds->leds[i]; + + led->regs = &cht_wc_led_regs[i]; + led->regmap = pmic->regmap; + mutex_init(&led->mutex); + led->cdev.name = cht_wc_leds_names[i]; + led->cdev.brightness_set_blocking = cht_wc_leds_brightness_set; + led->cdev.brightness_get = cht_wc_leds_brightness_get; + led->cdev.blink_set = cht_wc_leds_blink_set; + led->cdev.pattern_set = cht_wc_leds_pattern_set; + led->cdev.pattern_clear = cht_wc_leds_pattern_clear; + led->cdev.max_brightness = 255; + + ret = led_classdev_register(&pdev->dev, &led->cdev); + if (ret < 0) + return ret; + } + + platform_set_drvdata(pdev, leds); + return 0; +} + +static void cht_wc_leds_remove(struct platform_device *pdev) +{ + struct cht_wc_leds *leds = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < CHT_WC_LED_COUNT; i++) + led_classdev_unregister(&leds->leds[i].cdev); + + /* Restore LED1 regs if hw-control was active else leave LED1 off */ + if (!(leds->led1_initial_regs.ctrl & CHT_WC_LED1_SWCTL)) + cht_wc_led_restore_regs(&leds->leds[0], &leds->led1_initial_regs); +} + +static void cht_wc_leds_disable(struct platform_device *pdev) +{ + struct cht_wc_leds *leds = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < CHT_WC_LED_COUNT; i++) + cht_wc_leds_brightness_set(&leds->leds[i].cdev, 0); + + /* Restore LED1 regs if hw-control was active else leave LED1 off */ + if (!(leds->led1_initial_regs.ctrl & CHT_WC_LED1_SWCTL)) + cht_wc_led_restore_regs(&leds->leds[0], &leds->led1_initial_regs); +} + +/* On suspend save current settings and turn LEDs off */ +static int cht_wc_leds_suspend(struct device *dev) +{ + struct cht_wc_leds *leds = dev_get_drvdata(dev); + int i, ret; + + for (i = 0; i < CHT_WC_LED_COUNT; i++) { + ret = cht_wc_led_save_regs(&leds->leds[i], &leds->leds[i].saved_regs); + if (ret < 0) + return ret; + } + + cht_wc_leds_disable(to_platform_device(dev)); + return 0; +} + +/* On resume restore the saved settings */ +static int cht_wc_leds_resume(struct device *dev) +{ + struct cht_wc_leds *leds = dev_get_drvdata(dev); + int i; + + for (i = 0; i < CHT_WC_LED_COUNT; i++) + cht_wc_led_restore_regs(&leds->leds[i], &leds->leds[i].saved_regs); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(cht_wc_leds_pm, cht_wc_leds_suspend, cht_wc_leds_resume); + +static struct platform_driver cht_wc_leds_driver = { + .probe = cht_wc_leds_probe, + .remove_new = cht_wc_leds_remove, + .shutdown = cht_wc_leds_disable, + .driver = { + .name = "cht_wcove_leds", + .pm = pm_sleep_ptr(&cht_wc_leds_pm), + }, +}; +module_platform_driver(cht_wc_leds_driver); + +MODULE_ALIAS("platform:cht_wcove_leds"); +MODULE_DESCRIPTION("Intel Cherry Trail Whiskey Cove PMIC LEDs driver"); +MODULE_AUTHOR("Yauhen Kharuzhy <jekhor@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index ce4e79939731..7bfe40a6bfdd 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -13,6 +13,7 @@ #include <linux/leds.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/pinctrl/consumer.h> #include <linux/platform_device.h> #include <linux/property.h> #include <linux/slab.h> @@ -77,6 +78,7 @@ static int create_gpio_led(const struct gpio_led *template, struct fwnode_handle *fwnode, gpio_blink_set_t blink_set) { struct led_init_data init_data = {}; + struct pinctrl *pinctrl; int ret, state; led_dat->cdev.default_trigger = template->default_trigger; @@ -119,6 +121,22 @@ static int create_gpio_led(const struct gpio_led *template, &init_data); } + if (ret) + return ret; + + pinctrl = devm_pinctrl_get_select_default(led_dat->cdev.dev); + if (IS_ERR(pinctrl)) { + ret = PTR_ERR(pinctrl); + if (ret != -ENODEV) { + dev_warn(led_dat->cdev.dev, + "Failed to select %pOF pinctrl: %d\n", + to_of_node(fwnode), ret); + } else { + /* pinctrl-%d not present, not an error */ + ret = 0; + } + } + return ret; } diff --git a/drivers/leds/leds-is31fl319x.c b/drivers/leds/leds-is31fl319x.c index 7c908414ac7e..66c65741202e 100644 --- a/drivers/leds/leds-is31fl319x.c +++ b/drivers/leds/leds-is31fl319x.c @@ -602,7 +602,7 @@ static struct i2c_driver is31fl319x_driver = { .name = "leds-is31fl319x", .of_match_table = of_is31fl319x_match, }, - .probe_new = is31fl319x_probe, + .probe = is31fl319x_probe, .id_table = is31fl319x_id, }; diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c index 799191859ce0..72cb56d305c4 100644 --- a/drivers/leds/leds-is31fl32xx.c +++ b/drivers/leds/leds-is31fl32xx.c @@ -488,7 +488,7 @@ static struct i2c_driver is31fl32xx_driver = { .name = "is31fl32xx", .of_match_table = of_is31fl32xx_match, }, - .probe_new = is31fl32xx_probe, + .probe = is31fl32xx_probe, .remove = is31fl32xx_remove, .id_table = is31fl32xx_id, }; diff --git a/drivers/leds/leds-lm3530.c b/drivers/leds/leds-lm3530.c index a9a2018592ff..a2feef8e4ac5 100644 --- a/drivers/leds/leds-lm3530.c +++ b/drivers/leds/leds-lm3530.c @@ -484,7 +484,7 @@ static const struct i2c_device_id lm3530_id[] = { MODULE_DEVICE_TABLE(i2c, lm3530_id); static struct i2c_driver lm3530_i2c_driver = { - .probe_new = lm3530_probe, + .probe = lm3530_probe, .remove = lm3530_remove, .id_table = lm3530_id, .driver = { diff --git a/drivers/leds/leds-lm3532.c b/drivers/leds/leds-lm3532.c index a08c09129a68..13662a4aa1f2 100644 --- a/drivers/leds/leds-lm3532.c +++ b/drivers/leds/leds-lm3532.c @@ -726,7 +726,7 @@ static const struct i2c_device_id lm3532_id[] = { MODULE_DEVICE_TABLE(i2c, lm3532_id); static struct i2c_driver lm3532_i2c_driver = { - .probe_new = lm3532_probe, + .probe = lm3532_probe, .remove = lm3532_remove, .id_table = lm3532_id, .driver = { diff --git a/drivers/leds/leds-lm355x.c b/drivers/leds/leds-lm355x.c index 612873070ca4..f68771b9eac6 100644 --- a/drivers/leds/leds-lm355x.c +++ b/drivers/leds/leds-lm355x.c @@ -516,7 +516,7 @@ static struct i2c_driver lm355x_i2c_driver = { .name = LM355x_NAME, .pm = NULL, }, - .probe_new = lm355x_probe, + .probe = lm355x_probe, .remove = lm355x_remove, .id_table = lm355x_id, }; diff --git a/drivers/leds/leds-lm3642.c b/drivers/leds/leds-lm3642.c index b75ee3546c2e..6eee52e211be 100644 --- a/drivers/leds/leds-lm3642.c +++ b/drivers/leds/leds-lm3642.c @@ -401,7 +401,7 @@ static struct i2c_driver lm3642_i2c_driver = { .name = LM3642_NAME, .pm = NULL, }, - .probe_new = lm3642_probe, + .probe = lm3642_probe, .remove = lm3642_remove, .id_table = lm3642_id, }; diff --git a/drivers/leds/leds-lm3692x.c b/drivers/leds/leds-lm3692x.c index 66126d0666f5..f8ad61e47a19 100644 --- a/drivers/leds/leds-lm3692x.c +++ b/drivers/leds/leds-lm3692x.c @@ -518,7 +518,7 @@ static struct i2c_driver lm3692x_driver = { .name = "lm3692x", .of_match_table = of_lm3692x_leds_match, }, - .probe_new = lm3692x_probe, + .probe = lm3692x_probe, .remove = lm3692x_remove, .id_table = lm3692x_id, }; diff --git a/drivers/leds/leds-lm3697.c b/drivers/leds/leds-lm3697.c index 10e904bf40a0..cfb8ac220db6 100644 --- a/drivers/leds/leds-lm3697.c +++ b/drivers/leds/leds-lm3697.c @@ -376,7 +376,7 @@ static struct i2c_driver lm3697_driver = { .name = "lm3697", .of_match_table = of_lm3697_leds_match, }, - .probe_new = lm3697_probe, + .probe = lm3697_probe, .remove = lm3697_remove, .id_table = lm3697_id, }; diff --git a/drivers/leds/leds-lp3944.c b/drivers/leds/leds-lp3944.c index be47c66b2e00..8ea746c499d1 100644 --- a/drivers/leds/leds-lp3944.c +++ b/drivers/leds/leds-lp3944.c @@ -427,7 +427,7 @@ static struct i2c_driver lp3944_driver = { .driver = { .name = "lp3944", }, - .probe_new = lp3944_probe, + .probe = lp3944_probe, .remove = lp3944_remove, .id_table = lp3944_id, }; diff --git a/drivers/leds/leds-lp3952.c b/drivers/leds/leds-lp3952.c index 24b2e0f9080d..3bd55652a706 100644 --- a/drivers/leds/leds-lp3952.c +++ b/drivers/leds/leds-lp3952.c @@ -273,7 +273,7 @@ static struct i2c_driver lp3952_i2c_driver = { .driver = { .name = LP3952_NAME, }, - .probe_new = lp3952_probe, + .probe = lp3952_probe, .remove = lp3952_remove, .id_table = lp3952_id, }; diff --git a/drivers/leds/leds-lp50xx.c b/drivers/leds/leds-lp50xx.c index 28d6b39fa72d..68c4d9967d68 100644 --- a/drivers/leds/leds-lp50xx.c +++ b/drivers/leds/leds-lp50xx.c @@ -608,7 +608,7 @@ static struct i2c_driver lp50xx_driver = { .name = "lp50xx", .of_match_table = of_lp50xx_leds_match, }, - .probe_new = lp50xx_probe, + .probe = lp50xx_probe, .remove = lp50xx_remove, .id_table = lp50xx_id, }; diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c index a004af8e22c7..030c040fdf6d 100644 --- a/drivers/leds/leds-lp5521.c +++ b/drivers/leds/leds-lp5521.c @@ -58,14 +58,11 @@ /* CONFIG register */ #define LP5521_PWM_HF 0x40 /* PWM: 0 = 256Hz, 1 = 558Hz */ #define LP5521_PWRSAVE_EN 0x20 /* 1 = Power save mode */ -#define LP5521_CP_MODE_OFF 0 /* Charge pump (CP) off */ -#define LP5521_CP_MODE_BYPASS 8 /* CP forced to bypass mode */ -#define LP5521_CP_MODE_1X5 0x10 /* CP forced to 1.5x mode */ -#define LP5521_CP_MODE_AUTO 0x18 /* Automatic mode selection */ +#define LP5521_CP_MODE_MASK 0x18 /* Charge pump mode */ +#define LP5521_CP_MODE_SHIFT 3 #define LP5521_R_TO_BATT 0x04 /* R out: 0 = CP, 1 = Vbat */ #define LP5521_CLK_INT 0x01 /* Internal clock */ -#define LP5521_DEFAULT_CFG \ - (LP5521_PWM_HF | LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO) +#define LP5521_DEFAULT_CFG (LP5521_PWM_HF | LP5521_PWRSAVE_EN) /* Status */ #define LP5521_EXT_CLK_USED 0x08 @@ -310,6 +307,8 @@ static int lp5521_post_init_device(struct lp55xx_chip *chip) if (!lp55xx_is_extclk_used(chip)) val |= LP5521_CLK_INT; + val |= (chip->pdata->charge_pump_mode << LP5521_CP_MODE_SHIFT) & LP5521_CP_MODE_MASK; + ret = lp55xx_write(chip, LP5521_REG_CONFIG, val); if (ret) return ret; @@ -608,7 +607,7 @@ static struct i2c_driver lp5521_driver = { .name = "lp5521", .of_match_table = of_match_ptr(of_lp5521_leds_match), }, - .probe_new = lp5521_probe, + .probe = lp5521_probe, .remove = lp5521_remove, .id_table = lp5521_id, }; diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c index 55da914b8e5c..daa6a165fba6 100644 --- a/drivers/leds/leds-lp5523.c +++ b/drivers/leds/leds-lp5523.c @@ -57,8 +57,11 @@ #define LP5523_AUTO_INC 0x40 #define LP5523_PWR_SAVE 0x20 #define LP5523_PWM_PWR_SAVE 0x04 -#define LP5523_CP_AUTO 0x18 +#define LP5523_CP_MODE_MASK 0x18 +#define LP5523_CP_MODE_SHIFT 3 #define LP5523_AUTO_CLK 0x02 +#define LP5523_DEFAULT_CONFIG \ + (LP5523_AUTO_INC | LP5523_PWR_SAVE | LP5523_AUTO_CLK | LP5523_PWM_PWR_SAVE) #define LP5523_EN_LEDTEST 0x80 #define LP5523_LEDTEST_DONE 0x80 @@ -125,6 +128,7 @@ static void lp5523_set_led_current(struct lp55xx_led *led, u8 led_current) static int lp5523_post_init_device(struct lp55xx_chip *chip) { int ret; + int val; ret = lp55xx_write(chip, LP5523_REG_ENABLE, LP5523_ENABLE); if (ret) @@ -133,10 +137,10 @@ static int lp5523_post_init_device(struct lp55xx_chip *chip) /* Chip startup time is 500 us, 1 - 2 ms gives some margin */ usleep_range(1000, 2000); - ret = lp55xx_write(chip, LP5523_REG_CONFIG, - LP5523_AUTO_INC | LP5523_PWR_SAVE | - LP5523_CP_AUTO | LP5523_AUTO_CLK | - LP5523_PWM_PWR_SAVE); + val = LP5523_DEFAULT_CONFIG; + val |= (chip->pdata->charge_pump_mode << LP5523_CP_MODE_SHIFT) & LP5523_CP_MODE_MASK; + + ret = lp55xx_write(chip, LP5523_REG_CONFIG, val); if (ret) return ret; @@ -983,7 +987,7 @@ static struct i2c_driver lp5523_driver = { .name = "lp5523x", .of_match_table = of_match_ptr(of_lp5523_leds_match), }, - .probe_new = lp5523_probe, + .probe = lp5523_probe, .remove = lp5523_remove, .id_table = lp5523_id, }; diff --git a/drivers/leds/leds-lp5562.c b/drivers/leds/leds-lp5562.c index b5d877faf6d7..4565cc12cea8 100644 --- a/drivers/leds/leds-lp5562.c +++ b/drivers/leds/leds-lp5562.c @@ -603,7 +603,7 @@ static struct i2c_driver lp5562_driver = { .name = "lp5562", .of_match_table = of_match_ptr(of_lp5562_leds_match), }, - .probe_new = lp5562_probe, + .probe = lp5562_probe, .remove = lp5562_remove, .id_table = lp5562_id, }; diff --git a/drivers/leds/leds-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c index c1940964067a..77bb26906ea6 100644 --- a/drivers/leds/leds-lp55xx-common.c +++ b/drivers/leds/leds-lp55xx-common.c @@ -18,6 +18,7 @@ #include <linux/platform_data/leds-lp55xx.h> #include <linux/slab.h> #include <linux/gpio/consumer.h> +#include <dt-bindings/leds/leds-lp55xx.h> #include "leds-lp55xx-common.h" @@ -691,6 +692,14 @@ struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev, i++; } + if (of_property_read_u32(np, "ti,charge-pump-mode", &pdata->charge_pump_mode)) + pdata->charge_pump_mode = LP55XX_CP_AUTO; + + if (pdata->charge_pump_mode > LP55XX_CP_AUTO) { + dev_err(dev, "invalid charge pump mode %d\n", pdata->charge_pump_mode); + return ERR_PTR(-EINVAL); + } + of_property_read_string(np, "label", &pdata->label); of_property_read_u8(np, "clock-mode", &pdata->clock_mode); diff --git a/drivers/leds/leds-lp8501.c b/drivers/leds/leds-lp8501.c index 165d6423a928..f11886aa8965 100644 --- a/drivers/leds/leds-lp8501.c +++ b/drivers/leds/leds-lp8501.c @@ -53,10 +53,10 @@ #define LP8501_PWM_PSAVE BIT(7) #define LP8501_AUTO_INC BIT(6) #define LP8501_PWR_SAVE BIT(5) -#define LP8501_CP_AUTO 0x18 +#define LP8501_CP_MODE_MASK 0x18 +#define LP8501_CP_MODE_SHIFT 3 #define LP8501_INT_CLK BIT(0) -#define LP8501_DEFAULT_CFG \ - (LP8501_PWM_PSAVE | LP8501_AUTO_INC | LP8501_PWR_SAVE | LP8501_CP_AUTO) +#define LP8501_DEFAULT_CFG (LP8501_PWM_PSAVE | LP8501_AUTO_INC | LP8501_PWR_SAVE) #define LP8501_REG_RESET 0x3D #define LP8501_RESET 0xFF @@ -102,6 +102,8 @@ static int lp8501_post_init_device(struct lp55xx_chip *chip) if (chip->pdata->clock_mode != LP55XX_CLOCK_EXT) val |= LP8501_INT_CLK; + val |= (chip->pdata->charge_pump_mode << LP8501_CP_MODE_SHIFT) & LP8501_CP_MODE_MASK; + ret = lp55xx_write(chip, LP8501_REG_CONFIG, val); if (ret) return ret; @@ -392,7 +394,7 @@ static struct i2c_driver lp8501_driver = { .name = "lp8501", .of_match_table = of_match_ptr(of_lp8501_leds_match), }, - .probe_new = lp8501_probe, + .probe = lp8501_probe, .remove = lp8501_remove, .id_table = lp8501_id, }; diff --git a/drivers/leds/leds-lp8860.c b/drivers/leds/leds-lp8860.c index 221b386443bc..19b621012e58 100644 --- a/drivers/leds/leds-lp8860.c +++ b/drivers/leds/leds-lp8860.c @@ -475,7 +475,7 @@ static struct i2c_driver lp8860_driver = { .name = "lp8860", .of_match_table = of_lp8860_leds_match, }, - .probe_new = lp8860_probe, + .probe = lp8860_probe, .remove = lp8860_remove, .id_table = lp8860_id, }; diff --git a/drivers/leds/leds-mt6323.c b/drivers/leds/leds-mt6323.c index 17ee88043f52..24f35bdb55fb 100644 --- a/drivers/leds/leds-mt6323.c +++ b/drivers/leds/leds-mt6323.c @@ -14,61 +14,54 @@ #include <linux/regmap.h> /* - * Register field for MT6323_TOP_CKPDN0 to enable + * Register field for TOP_CKPDN0 to enable * 32K clock common for LED device. */ -#define MT6323_RG_DRV_32K_CK_PDN BIT(11) -#define MT6323_RG_DRV_32K_CK_PDN_MASK BIT(11) +#define RG_DRV_32K_CK_PDN BIT(11) +#define RG_DRV_32K_CK_PDN_MASK BIT(11) + +/* 32K/1M/6M clock common for WLED device */ +#define RG_VWLED_1M_CK_PDN BIT(0) +#define RG_VWLED_32K_CK_PDN BIT(12) +#define RG_VWLED_6M_CK_PDN BIT(13) /* - * Register field for MT6323_TOP_CKPDN2 to enable + * Register field for TOP_CKPDN2 to enable * individual clock for LED device. */ -#define MT6323_RG_ISINK_CK_PDN(i) BIT(i) -#define MT6323_RG_ISINK_CK_PDN_MASK(i) BIT(i) +#define RG_ISINK_CK_PDN(i) BIT(i) +#define RG_ISINK_CK_PDN_MASK(i) BIT(i) /* - * Register field for MT6323_TOP_CKCON1 to select + * Register field for TOP_CKCON1 to select * clock source. */ -#define MT6323_RG_ISINK_CK_SEL_MASK(i) (BIT(10) << (i)) +#define RG_ISINK_CK_SEL_MASK(i) (BIT(10) << (i)) -/* - * Register for MT6323_ISINK_CON0 to setup the - * duty cycle of the blink. - */ -#define MT6323_ISINK_CON0(i) (MT6323_ISINK0_CON0 + 0x8 * (i)) -#define MT6323_ISINK_DIM_DUTY_MASK (0x1f << 8) -#define MT6323_ISINK_DIM_DUTY(i) (((i) << 8) & \ - MT6323_ISINK_DIM_DUTY_MASK) - -/* Register to setup the period of the blink. */ -#define MT6323_ISINK_CON1(i) (MT6323_ISINK0_CON1 + 0x8 * (i)) -#define MT6323_ISINK_DIM_FSEL_MASK (0xffff) -#define MT6323_ISINK_DIM_FSEL(i) ((i) & MT6323_ISINK_DIM_FSEL_MASK) - -/* Register to control the brightness. */ -#define MT6323_ISINK_CON2(i) (MT6323_ISINK0_CON2 + 0x8 * (i)) -#define MT6323_ISINK_CH_STEP_SHIFT 12 -#define MT6323_ISINK_CH_STEP_MASK (0x7 << 12) -#define MT6323_ISINK_CH_STEP(i) (((i) << 12) & \ - MT6323_ISINK_CH_STEP_MASK) -#define MT6323_ISINK_SFSTR0_TC_MASK (0x3 << 1) -#define MT6323_ISINK_SFSTR0_TC(i) (((i) << 1) & \ - MT6323_ISINK_SFSTR0_TC_MASK) -#define MT6323_ISINK_SFSTR0_EN_MASK BIT(0) -#define MT6323_ISINK_SFSTR0_EN BIT(0) +#define ISINK_CON(r, i) (r + 0x8 * (i)) + +/* ISINK_CON0: Register to setup the duty cycle of the blink. */ +#define ISINK_DIM_DUTY_MASK (0x1f << 8) +#define ISINK_DIM_DUTY(i) (((i) << 8) & ISINK_DIM_DUTY_MASK) + +/* ISINK_CON1: Register to setup the period of the blink. */ +#define ISINK_DIM_FSEL_MASK (0xffff) +#define ISINK_DIM_FSEL(i) ((i) & ISINK_DIM_FSEL_MASK) + +/* ISINK_CON2: Register to control the brightness. */ +#define ISINK_CH_STEP_SHIFT 12 +#define ISINK_CH_STEP_MASK (0x7 << 12) +#define ISINK_CH_STEP(i) (((i) << 12) & ISINK_CH_STEP_MASK) +#define ISINK_SFSTR0_TC_MASK (0x3 << 1) +#define ISINK_SFSTR0_TC(i) (((i) << 1) & ISINK_SFSTR0_TC_MASK) +#define ISINK_SFSTR0_EN_MASK BIT(0) +#define ISINK_SFSTR0_EN BIT(0) /* Register to LED channel enablement. */ -#define MT6323_ISINK_CH_EN_MASK(i) BIT(i) -#define MT6323_ISINK_CH_EN(i) BIT(i) +#define ISINK_CH_EN_MASK(i) BIT(i) +#define ISINK_CH_EN(i) BIT(i) -#define MT6323_MAX_PERIOD 10000 -#define MT6323_MAX_LEDS 4 -#define MT6323_MAX_BRIGHTNESS 6 -#define MT6323_UNIT_DUTY 3125 -#define MT6323_CAL_HW_DUTY(o, p) DIV_ROUND_CLOSEST((o) * 100000ul,\ - (p) * MT6323_UNIT_DUTY) +#define MAX_SUPPORTED_LEDS 8 struct mt6323_leds; @@ -87,11 +80,62 @@ struct mt6323_led { }; /** + * struct mt6323_regs - register spec for the LED device + * @top_ckpdn: Offset to ISINK_CKPDN[0..x] registers + * @num_top_ckpdn: Number of ISINK_CKPDN registers + * @top_ckcon: Offset to ISINK_CKCON[0..x] registers + * @num_top_ckcon: Number of ISINK_CKCON registers + * @isink_con: Offset to ISINKx_CON[0..x] registers + * @num_isink_con: Number of ISINKx_CON registers + * @isink_max_regs: Number of ISINK[0..x] registers + * @isink_en_ctrl: Offset to ISINK_EN_CTRL register + * @iwled_en_ctrl: Offset to IWLED_EN_CTRL register + */ +struct mt6323_regs { + const u16 *top_ckpdn; + u8 num_top_ckpdn; + const u16 *top_ckcon; + u8 num_top_ckcon; + const u16 *isink_con; + u8 num_isink_con; + u8 isink_max_regs; + u16 isink_en_ctrl; + u16 iwled_en_ctrl; +}; + +/** + * struct mt6323_hwspec - hardware specific parameters + * @max_period: Maximum period for all LEDs + * @max_leds: Maximum number of supported LEDs + * @max_wleds: Maximum number of WLEDs + * @max_brightness: Maximum brightness for all LEDs + * @unit_duty: Steps of duty per period + */ +struct mt6323_hwspec { + u16 max_period; + u8 max_leds; + u8 max_wleds; + u16 max_brightness; + u16 unit_duty; +}; + +/** + * struct mt6323_data - device specific data + * @regs: Register spec for this device + * @spec: Hardware specific parameters + */ +struct mt6323_data { + const struct mt6323_regs *regs; + const struct mt6323_hwspec *spec; +}; + +/** * struct mt6323_leds - state container for holding LED controller * of the driver * @dev: the device pointer * @hw: the underlying hardware providing shared * bus for the register operations + * @pdata: device specific data * @lock: the lock among process context * @led: the array that contains the state of individual * LED device @@ -99,9 +143,10 @@ struct mt6323_led { struct mt6323_leds { struct device *dev; struct mt6397_chip *hw; + const struct mt6323_data *pdata; /* protect among process context */ struct mutex lock; - struct mt6323_led *led[MT6323_MAX_LEDS]; + struct mt6323_led *led[MAX_SUPPORTED_LEDS]; }; static int mt6323_led_hw_brightness(struct led_classdev *cdev, @@ -109,6 +154,7 @@ static int mt6323_led_hw_brightness(struct led_classdev *cdev, { struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev); struct mt6323_leds *leds = led->parent; + const struct mt6323_regs *regs = leds->pdata->regs; struct regmap *regmap = leds->hw->regmap; u32 con2_mask = 0, con2_val = 0; int ret; @@ -117,14 +163,14 @@ static int mt6323_led_hw_brightness(struct led_classdev *cdev, * Setup current output for the corresponding * brightness level. */ - con2_mask |= MT6323_ISINK_CH_STEP_MASK | - MT6323_ISINK_SFSTR0_TC_MASK | - MT6323_ISINK_SFSTR0_EN_MASK; - con2_val |= MT6323_ISINK_CH_STEP(brightness - 1) | - MT6323_ISINK_SFSTR0_TC(2) | - MT6323_ISINK_SFSTR0_EN; - - ret = regmap_update_bits(regmap, MT6323_ISINK_CON2(led->id), + con2_mask |= ISINK_CH_STEP_MASK | + ISINK_SFSTR0_TC_MASK | + ISINK_SFSTR0_EN_MASK; + con2_val |= ISINK_CH_STEP(brightness - 1) | + ISINK_SFSTR0_TC(2) | + ISINK_SFSTR0_EN; + + ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[2], led->id), con2_mask, con2_val); return ret; } @@ -133,20 +179,21 @@ static int mt6323_led_hw_off(struct led_classdev *cdev) { struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev); struct mt6323_leds *leds = led->parent; + const struct mt6323_regs *regs = leds->pdata->regs; struct regmap *regmap = leds->hw->regmap; unsigned int status; int ret; - status = MT6323_ISINK_CH_EN(led->id); - ret = regmap_update_bits(regmap, MT6323_ISINK_EN_CTRL, - MT6323_ISINK_CH_EN_MASK(led->id), ~status); + status = ISINK_CH_EN(led->id); + ret = regmap_update_bits(regmap, regs->isink_en_ctrl, + ISINK_CH_EN_MASK(led->id), ~status); if (ret < 0) return ret; usleep_range(100, 300); - ret = regmap_update_bits(regmap, MT6323_TOP_CKPDN2, - MT6323_RG_ISINK_CK_PDN_MASK(led->id), - MT6323_RG_ISINK_CK_PDN(led->id)); + ret = regmap_update_bits(regmap, regs->top_ckpdn[2], + RG_ISINK_CK_PDN_MASK(led->id), + RG_ISINK_CK_PDN(led->id)); if (ret < 0) return ret; @@ -158,30 +205,31 @@ mt6323_get_led_hw_brightness(struct led_classdev *cdev) { struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev); struct mt6323_leds *leds = led->parent; + const struct mt6323_regs *regs = leds->pdata->regs; struct regmap *regmap = leds->hw->regmap; unsigned int status; int ret; - ret = regmap_read(regmap, MT6323_TOP_CKPDN2, &status); + ret = regmap_read(regmap, regs->top_ckpdn[2], &status); if (ret < 0) return ret; - if (status & MT6323_RG_ISINK_CK_PDN_MASK(led->id)) + if (status & RG_ISINK_CK_PDN_MASK(led->id)) return 0; - ret = regmap_read(regmap, MT6323_ISINK_EN_CTRL, &status); + ret = regmap_read(regmap, regs->isink_en_ctrl, &status); if (ret < 0) return ret; - if (!(status & MT6323_ISINK_CH_EN(led->id))) + if (!(status & ISINK_CH_EN(led->id))) return 0; - ret = regmap_read(regmap, MT6323_ISINK_CON2(led->id), &status); + ret = regmap_read(regmap, ISINK_CON(regs->isink_con[2], led->id), &status); if (ret < 0) return ret; - return ((status & MT6323_ISINK_CH_STEP_MASK) - >> MT6323_ISINK_CH_STEP_SHIFT) + 1; + return ((status & ISINK_CH_STEP_MASK) + >> ISINK_CH_STEP_SHIFT) + 1; } static int mt6323_led_hw_on(struct led_classdev *cdev, @@ -189,6 +237,7 @@ static int mt6323_led_hw_on(struct led_classdev *cdev, { struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev); struct mt6323_leds *leds = led->parent; + const struct mt6323_regs *regs = leds->pdata->regs; struct regmap *regmap = leds->hw->regmap; unsigned int status; int ret; @@ -198,23 +247,23 @@ static int mt6323_led_hw_on(struct led_classdev *cdev, * clock and channel and let work with continuous blink as * the default. */ - ret = regmap_update_bits(regmap, MT6323_TOP_CKCON1, - MT6323_RG_ISINK_CK_SEL_MASK(led->id), 0); + ret = regmap_update_bits(regmap, regs->top_ckcon[1], + RG_ISINK_CK_SEL_MASK(led->id), 0); if (ret < 0) return ret; - status = MT6323_RG_ISINK_CK_PDN(led->id); - ret = regmap_update_bits(regmap, MT6323_TOP_CKPDN2, - MT6323_RG_ISINK_CK_PDN_MASK(led->id), + status = RG_ISINK_CK_PDN(led->id); + ret = regmap_update_bits(regmap, regs->top_ckpdn[2], + RG_ISINK_CK_PDN_MASK(led->id), ~status); if (ret < 0) return ret; usleep_range(100, 300); - ret = regmap_update_bits(regmap, MT6323_ISINK_EN_CTRL, - MT6323_ISINK_CH_EN_MASK(led->id), - MT6323_ISINK_CH_EN(led->id)); + ret = regmap_update_bits(regmap, regs->isink_en_ctrl, + ISINK_CH_EN_MASK(led->id), + ISINK_CH_EN(led->id)); if (ret < 0) return ret; @@ -222,15 +271,15 @@ static int mt6323_led_hw_on(struct led_classdev *cdev, if (ret < 0) return ret; - ret = regmap_update_bits(regmap, MT6323_ISINK_CON0(led->id), - MT6323_ISINK_DIM_DUTY_MASK, - MT6323_ISINK_DIM_DUTY(31)); + ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[0], led->id), + ISINK_DIM_DUTY_MASK, + ISINK_DIM_DUTY(31)); if (ret < 0) return ret; - ret = regmap_update_bits(regmap, MT6323_ISINK_CON1(led->id), - MT6323_ISINK_DIM_FSEL_MASK, - MT6323_ISINK_DIM_FSEL(1000)); + ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[1], led->id), + ISINK_DIM_FSEL_MASK, + ISINK_DIM_FSEL(1000)); if (ret < 0) return ret; @@ -243,6 +292,8 @@ static int mt6323_led_set_blink(struct led_classdev *cdev, { struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev); struct mt6323_leds *leds = led->parent; + const struct mt6323_regs *regs = leds->pdata->regs; + const struct mt6323_hwspec *spec = leds->pdata->spec; struct regmap *regmap = leds->hw->regmap; unsigned long period; u8 duty_hw; @@ -265,14 +316,14 @@ static int mt6323_led_set_blink(struct led_classdev *cdev, */ period = *delay_on + *delay_off; - if (period > MT6323_MAX_PERIOD) + if (period > spec->max_period) return -EINVAL; /* * Calculate duty_hw based on the percentage of period during * which the led is ON. */ - duty_hw = MT6323_CAL_HW_DUTY(*delay_on, period); + duty_hw = DIV_ROUND_CLOSEST(*delay_on * 100000ul, period * spec->unit_duty); /* hardware doesn't support zero duty cycle. */ if (!duty_hw) @@ -290,15 +341,15 @@ static int mt6323_led_set_blink(struct led_classdev *cdev, led->current_brightness = cdev->max_brightness; } - ret = regmap_update_bits(regmap, MT6323_ISINK_CON0(led->id), - MT6323_ISINK_DIM_DUTY_MASK, - MT6323_ISINK_DIM_DUTY(duty_hw - 1)); + ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[0], led->id), + ISINK_DIM_DUTY_MASK, + ISINK_DIM_DUTY(duty_hw - 1)); if (ret < 0) goto out; - ret = regmap_update_bits(regmap, MT6323_ISINK_CON1(led->id), - MT6323_ISINK_DIM_FSEL_MASK, - MT6323_ISINK_DIM_FSEL(period - 1)); + ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[1], led->id), + ISINK_DIM_FSEL_MASK, + ISINK_DIM_FSEL(period - 1)); out: mutex_unlock(&leds->lock); @@ -335,6 +386,117 @@ out: return ret; } +static int mtk_wled_hw_on(struct led_classdev *cdev) +{ + struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev); + struct mt6323_leds *leds = led->parent; + const struct mt6323_regs *regs = leds->pdata->regs; + struct regmap *regmap = leds->hw->regmap; + int ret; + + ret = regmap_clear_bits(regmap, regs->top_ckpdn[0], RG_VWLED_32K_CK_PDN); + if (ret) + return ret; + + ret = regmap_clear_bits(regmap, regs->top_ckpdn[0], RG_VWLED_6M_CK_PDN); + if (ret) + return ret; + + ret = regmap_clear_bits(regmap, regs->top_ckpdn[0], RG_VWLED_1M_CK_PDN); + if (ret) + return ret; + + usleep_range(5000, 6000); + + /* Enable WLED channel pair */ + ret = regmap_set_bits(regmap, regs->iwled_en_ctrl, BIT(led->id)); + if (ret) + return ret; + + ret = regmap_set_bits(regmap, regs->iwled_en_ctrl, BIT(led->id + 1)); + if (ret) + return ret; + + return 0; +} + +static int mtk_wled_hw_off(struct led_classdev *cdev) +{ + struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev); + struct mt6323_leds *leds = led->parent; + const struct mt6323_regs *regs = leds->pdata->regs; + struct regmap *regmap = leds->hw->regmap; + int ret; + + ret = regmap_clear_bits(regmap, regs->iwled_en_ctrl, BIT(led->id + 1)); + if (ret) + return ret; + + ret = regmap_clear_bits(regmap, regs->iwled_en_ctrl, BIT(led->id)); + if (ret) + return ret; + + ret = regmap_set_bits(regmap, regs->top_ckpdn[0], RG_VWLED_32K_CK_PDN); + if (ret) + return ret; + + ret = regmap_set_bits(regmap, regs->top_ckpdn[0], RG_VWLED_6M_CK_PDN); + if (ret) + return ret; + + ret = regmap_set_bits(regmap, regs->top_ckpdn[0], RG_VWLED_1M_CK_PDN); + if (ret) + return ret; + + return 0; +} + +static enum led_brightness mt6323_get_wled_brightness(struct led_classdev *cdev) +{ + struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev); + struct mt6323_leds *leds = led->parent; + const struct mt6323_regs *regs = leds->pdata->regs; + struct regmap *regmap = leds->hw->regmap; + unsigned int status; + int ret; + + ret = regmap_read(regmap, regs->iwled_en_ctrl, &status); + if (ret) + return 0; + + /* Always two channels per WLED */ + status &= BIT(led->id) | BIT(led->id + 1); + + return status ? led->current_brightness : 0; +} + +static int mt6323_wled_set_brightness(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev); + struct mt6323_leds *leds = led->parent; + int ret = 0; + + mutex_lock(&leds->lock); + + if (brightness) { + if (!led->current_brightness) + ret = mtk_wled_hw_on(cdev); + if (ret) + goto out; + } else { + ret = mtk_wled_hw_off(cdev); + if (ret) + goto out; + } + + led->current_brightness = brightness; +out: + mutex_unlock(&leds->lock); + + return ret; +} + static int mt6323_led_set_dt_default(struct led_classdev *cdev, struct device_node *np) { @@ -369,9 +531,12 @@ static int mt6323_led_probe(struct platform_device *pdev) struct mt6397_chip *hw = dev_get_drvdata(dev->parent); struct mt6323_leds *leds; struct mt6323_led *led; + const struct mt6323_regs *regs; + const struct mt6323_hwspec *spec; int ret; unsigned int status; u32 reg; + u8 max_leds; leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL); if (!leds) @@ -379,6 +544,10 @@ static int mt6323_led_probe(struct platform_device *pdev) platform_set_drvdata(pdev, leds); leds->dev = dev; + leds->pdata = device_get_match_data(dev); + regs = leds->pdata->regs; + spec = leds->pdata->spec; + max_leds = spec->max_leds + spec->max_wleds; /* * leds->hw points to the underlying bus for the register @@ -387,17 +556,18 @@ static int mt6323_led_probe(struct platform_device *pdev) leds->hw = hw; mutex_init(&leds->lock); - status = MT6323_RG_DRV_32K_CK_PDN; - ret = regmap_update_bits(leds->hw->regmap, MT6323_TOP_CKPDN0, - MT6323_RG_DRV_32K_CK_PDN_MASK, ~status); + status = RG_DRV_32K_CK_PDN; + ret = regmap_update_bits(leds->hw->regmap, regs->top_ckpdn[0], + RG_DRV_32K_CK_PDN_MASK, ~status); if (ret < 0) { dev_err(leds->dev, - "Failed to update MT6323_TOP_CKPDN0 Register\n"); + "Failed to update TOP_CKPDN0 Register\n"); return ret; } for_each_available_child_of_node(np, child) { struct led_init_data init_data = {}; + bool is_wled; ret = of_property_read_u32(child, "reg", ®); if (ret) { @@ -405,7 +575,8 @@ static int mt6323_led_probe(struct platform_device *pdev) goto put_child_node; } - if (reg >= MT6323_MAX_LEDS || leds->led[reg]) { + if (reg >= max_leds || reg >= MAX_SUPPORTED_LEDS || + leds->led[reg]) { dev_err(dev, "Invalid led reg %u\n", reg); ret = -EINVAL; goto put_child_node; @@ -417,14 +588,24 @@ static int mt6323_led_probe(struct platform_device *pdev) goto put_child_node; } + is_wled = of_property_read_bool(child, "mediatek,is-wled"); + leds->led[reg] = led; leds->led[reg]->id = reg; - leds->led[reg]->cdev.max_brightness = MT6323_MAX_BRIGHTNESS; - leds->led[reg]->cdev.brightness_set_blocking = - mt6323_led_set_brightness; - leds->led[reg]->cdev.blink_set = mt6323_led_set_blink; - leds->led[reg]->cdev.brightness_get = - mt6323_get_led_hw_brightness; + leds->led[reg]->cdev.max_brightness = spec->max_brightness; + + if (is_wled) { + leds->led[reg]->cdev.brightness_set_blocking = + mt6323_wled_set_brightness; + leds->led[reg]->cdev.brightness_get = + mt6323_get_wled_brightness; + } else { + leds->led[reg]->cdev.brightness_set_blocking = + mt6323_led_set_brightness; + leds->led[reg]->cdev.blink_set = mt6323_led_set_blink; + leds->led[reg]->cdev.brightness_get = + mt6323_get_led_hw_brightness; + } leds->led[reg]->parent = leds; ret = mt6323_led_set_dt_default(&leds->led[reg]->cdev, child); @@ -454,23 +635,88 @@ put_child_node: static int mt6323_led_remove(struct platform_device *pdev) { struct mt6323_leds *leds = platform_get_drvdata(pdev); + const struct mt6323_regs *regs = leds->pdata->regs; int i; /* Turn the LEDs off on driver removal. */ for (i = 0 ; leds->led[i] ; i++) mt6323_led_hw_off(&leds->led[i]->cdev); - regmap_update_bits(leds->hw->regmap, MT6323_TOP_CKPDN0, - MT6323_RG_DRV_32K_CK_PDN_MASK, - MT6323_RG_DRV_32K_CK_PDN); + regmap_update_bits(leds->hw->regmap, regs->top_ckpdn[0], + RG_DRV_32K_CK_PDN_MASK, + RG_DRV_32K_CK_PDN); mutex_destroy(&leds->lock); return 0; } +static const struct mt6323_regs mt6323_registers = { + .top_ckpdn = (const u16[]){ 0x102, 0x106, 0x10e }, + .num_top_ckpdn = 3, + .top_ckcon = (const u16[]){ 0x120, 0x126 }, + .num_top_ckcon = 2, + .isink_con = (const u16[]){ 0x330, 0x332, 0x334 }, + .num_isink_con = 3, + .isink_max_regs = 4, /* ISINK[0..3] */ + .isink_en_ctrl = 0x356, +}; + +static const struct mt6323_regs mt6331_registers = { + .top_ckpdn = (const u16[]){ 0x138, 0x13e, 0x144 }, + .num_top_ckpdn = 3, + .top_ckcon = (const u16[]){ 0x14c, 0x14a }, + .num_top_ckcon = 2, + .isink_con = (const u16[]){ 0x40c, 0x40e, 0x410, 0x412, 0x414 }, + .num_isink_con = 5, + .isink_max_regs = 4, /* ISINK[0..3] */ + .isink_en_ctrl = 0x43a, +}; + +static const struct mt6323_regs mt6332_registers = { + .top_ckpdn = (const u16[]){ 0x8094, 0x809a, 0x80a0 }, + .num_top_ckpdn = 3, + .top_ckcon = (const u16[]){ 0x80a6, 0x80ac }, + .num_top_ckcon = 2, + .isink_con = (const u16[]){ 0x8cd4 }, + .num_isink_con = 1, + .isink_max_regs = 12, /* IWLED[0..2, 3..9] */ + .iwled_en_ctrl = 0x8cda, +}; + +static const struct mt6323_hwspec mt6323_spec = { + .max_period = 10000, + .max_leds = 4, + .max_brightness = 6, + .unit_duty = 3125, +}; + +static const struct mt6323_hwspec mt6332_spec = { + /* There are no LEDs in MT6332. Only WLEDs are present. */ + .max_leds = 0, + .max_wleds = 1, + .max_brightness = 1024, +}; + +static const struct mt6323_data mt6323_pdata = { + .regs = &mt6323_registers, + .spec = &mt6323_spec, +}; + +static const struct mt6323_data mt6331_pdata = { + .regs = &mt6331_registers, + .spec = &mt6323_spec, +}; + +static const struct mt6323_data mt6332_pdata = { + .regs = &mt6332_registers, + .spec = &mt6332_spec, +}; + static const struct of_device_id mt6323_led_dt_match[] = { - { .compatible = "mediatek,mt6323-led" }, + { .compatible = "mediatek,mt6323-led", .data = &mt6323_pdata}, + { .compatible = "mediatek,mt6331-led", .data = &mt6331_pdata }, + { .compatible = "mediatek,mt6332-led", .data = &mt6332_pdata }, {}, }; MODULE_DEVICE_TABLE(of, mt6323_led_dt_match); diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c index 15b1acfa442e..8b5c62083e50 100644 --- a/drivers/leds/leds-pca9532.c +++ b/drivers/leds/leds-pca9532.c @@ -102,7 +102,7 @@ static struct i2c_driver pca9532_driver = { .name = "leds-pca953x", .of_match_table = of_match_ptr(of_pca9532_leds_match), }, - .probe_new = pca9532_probe, + .probe = pca9532_probe, .remove = pca9532_remove, .id_table = pca9532_id, }; diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c index 1edd092e7894..b10e1ef38db0 100644 --- a/drivers/leds/leds-pca955x.c +++ b/drivers/leds/leds-pca955x.c @@ -668,7 +668,7 @@ static struct i2c_driver pca955x_driver = { .name = "leds-pca955x", .of_match_table = of_pca955x_match, }, - .probe_new = pca955x_probe, + .probe = pca955x_probe, .id_table = pca955x_id, }; diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index 9cd476db601f..47223c850e4b 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -431,7 +431,7 @@ static struct i2c_driver pca963x_driver = { .name = "leds-pca963x", .of_match_table = of_pca963x_match, }, - .probe_new = pca963x_probe, + .probe = pca963x_probe, .id_table = pca963x_id, }; diff --git a/drivers/leds/leds-spi-byte.c b/drivers/leds/leds-spi-byte.c index 2bc5c99daf51..2c7ffc3c78e6 100644 --- a/drivers/leds/leds-spi-byte.c +++ b/drivers/leds/leds-spi-byte.c @@ -98,7 +98,7 @@ static int spi_byte_probe(struct spi_device *spi) return -ENOMEM; of_property_read_string(child, "label", &name); - strlcpy(led->name, name, sizeof(led->name)); + strscpy(led->name, name, sizeof(led->name)); led->spi = spi; mutex_init(&led->mutex); led->cdef = device_get_match_data(dev); diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c index 634cabd5bb79..aab861771210 100644 --- a/drivers/leds/leds-tca6507.c +++ b/drivers/leds/leds-tca6507.c @@ -808,7 +808,7 @@ static struct i2c_driver tca6507_driver = { .name = "leds-tca6507", .of_match_table = of_match_ptr(of_tca6507_leds_match), }, - .probe_new = tca6507_probe, + .probe = tca6507_probe, .remove = tca6507_remove, .id_table = tca6507_id, }; diff --git a/drivers/leds/leds-tlc591xx.c b/drivers/leds/leds-tlc591xx.c index 7e31db50036f..dfc6fb2b3e52 100644 --- a/drivers/leds/leds-tlc591xx.c +++ b/drivers/leds/leds-tlc591xx.c @@ -230,7 +230,7 @@ static struct i2c_driver tlc591xx_driver = { .name = "tlc591xx", .of_match_table = of_match_ptr(of_tlc591xx_leds_match), }, - .probe_new = tlc591xx_probe, + .probe = tlc591xx_probe, .id_table = tlc591xx_id, }; diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index 013f551b32b2..64b2d7b6d3f3 100644 --- a/drivers/leds/leds-turris-omnia.c +++ b/drivers/leds/leds-turris-omnia.c @@ -271,7 +271,7 @@ static const struct i2c_device_id omnia_id[] = { MODULE_DEVICE_TABLE(i2c, omnia_id); static struct i2c_driver omnia_leds_driver = { - .probe_new = omnia_leds_probe, + .probe = omnia_leds_probe, .remove = omnia_leds_remove, .id_table = omnia_id, .driver = { diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c index 1c849814a491..59581b3e25ca 100644 --- a/drivers/leds/rgb/leds-qcom-lpg.c +++ b/drivers/leds/rgb/leds-qcom-lpg.c @@ -1173,8 +1173,10 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) i = 0; for_each_available_child_of_node(np, child) { ret = lpg_parse_channel(lpg, child, &led->channels[i]); - if (ret < 0) + if (ret < 0) { + of_node_put(child); return ret; + } info[i].color_index = led->channels[i]->color; info[i].intensity = 0; @@ -1352,8 +1354,10 @@ static int lpg_probe(struct platform_device *pdev) for_each_available_child_of_node(pdev->dev.of_node, np) { ret = lpg_add_led(lpg, np); - if (ret) + if (ret) { + of_node_put(np); return ret; + } } for (i = 0; i < lpg->num_channels; i++) @@ -1414,6 +1418,20 @@ static const struct lpg_data pm8994_lpg_data = { }, }; +/* PMI632 uses SDAM instead of LUT for pattern */ +static const struct lpg_data pmi632_lpg_data = { + .triled_base = 0xd000, + + .num_channels = 5, + .channels = (const struct lpg_channel_data[]) { + { .base = 0xb300, .triled_mask = BIT(7) }, + { .base = 0xb400, .triled_mask = BIT(6) }, + { .base = 0xb500, .triled_mask = BIT(5) }, + { .base = 0xb600 }, + { .base = 0xb700 }, + }, +}; + static const struct lpg_data pmi8994_lpg_data = { .lut_base = 0xb000, .lut_size = 24, @@ -1505,6 +1523,7 @@ static const struct of_device_id lpg_of_table[] = { { .compatible = "qcom,pm8916-pwm", .data = &pm8916_pwm_data }, { .compatible = "qcom,pm8941-lpg", .data = &pm8941_lpg_data }, { .compatible = "qcom,pm8994-lpg", .data = &pm8994_lpg_data }, + { .compatible = "qcom,pmi632-lpg", .data = &pmi632_lpg_data }, { .compatible = "qcom,pmi8994-lpg", .data = &pmi8994_lpg_data }, { .compatible = "qcom,pmi8998-lpg", .data = &pmi8998_lpg_data }, { .compatible = "qcom,pmc8180c-lpg", .data = &pm8150l_lpg_data }, diff --git a/drivers/leds/simple/Kconfig b/drivers/leds/simple/Kconfig index fd2b8225d926..44fa0f93cb3b 100644 --- a/drivers/leds/simple/Kconfig +++ b/drivers/leds/simple/Kconfig @@ -1,11 +1,36 @@ # SPDX-License-Identifier: GPL-2.0-only config LEDS_SIEMENS_SIMATIC_IPC tristate "LED driver for Siemens Simatic IPCs" - depends on LEDS_GPIO depends on SIEMENS_SIMATIC_IPC help This option enables support for the LEDs of several Industrial PCs from Siemens. - To compile this driver as a module, choose M here: the modules - will be called simatic-ipc-leds and simatic-ipc-leds-gpio. + To compile this driver as a module, choose M here: the module + will be called simatic-ipc-leds. + +config LEDS_SIEMENS_SIMATIC_IPC_APOLLOLAKE + tristate "LED driver for Siemens Simatic IPCs based on Intel Apollo Lake GPIO" + depends on LEDS_GPIO + depends on PINCTRL_BROXTON + depends on SIEMENS_SIMATIC_IPC + default LEDS_SIEMENS_SIMATIC_IPC + help + This option enables support for the LEDs of several Industrial PCs + from Siemens based on Apollo Lake GPIO i.e. IPC127E. + + To compile this driver as a module, choose M here: the module + will be called simatic-ipc-leds-gpio-apollolake. + +config LEDS_SIEMENS_SIMATIC_IPC_F7188X + tristate "LED driver for Siemens Simatic IPCs based on Nuvoton GPIO" + depends on LEDS_GPIO + depends on GPIO_F7188X + depends on SIEMENS_SIMATIC_IPC + default LEDS_SIEMENS_SIMATIC_IPC + help + This option enables support for the LEDs of several Industrial PCs + from Siemens based on Nuvoton GPIO i.e. IPC227G. + + To compile this driver as a module, choose M here: the module + will be called simatic-ipc-leds-gpio-f7188x. diff --git a/drivers/leds/simple/Makefile b/drivers/leds/simple/Makefile index 1c7ef5e1324b..e3e840cea275 100644 --- a/drivers/leds/simple/Makefile +++ b/drivers/leds/simple/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 -obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC) += simatic-ipc-leds.o -obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC) += simatic-ipc-leds-gpio.o +obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC) += simatic-ipc-leds.o +obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC_APOLLOLAKE) += simatic-ipc-leds-gpio-core.o simatic-ipc-leds-gpio-apollolake.o +obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC_F7188X) += simatic-ipc-leds-gpio-core.o simatic-ipc-leds-gpio-f7188x.o diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio-apollolake.c b/drivers/leds/simple/simatic-ipc-leds-gpio-apollolake.c new file mode 100644 index 000000000000..e1c712729dcf --- /dev/null +++ b/drivers/leds/simple/simatic-ipc-leds-gpio-apollolake.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Siemens SIMATIC IPC driver for GPIO based LEDs + * + * Copyright (c) Siemens AG, 2023 + * + * Author: + * Henning Schild <henning.schild@siemens.com> + */ + +#include <linux/gpio/machine.h> +#include <linux/gpio/consumer.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/platform_data/x86/simatic-ipc-base.h> + +#include "simatic-ipc-leds-gpio.h" + +static struct gpiod_lookup_table simatic_ipc_led_gpio_table = { + .dev_id = "leds-gpio", + .table = { + GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 52, NULL, 0, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 53, NULL, 1, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 57, NULL, 2, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 58, NULL, 3, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 60, NULL, 4, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 51, NULL, 5, GPIO_ACTIVE_LOW), + {} /* Terminating entry */ + }, +}; + +static struct gpiod_lookup_table simatic_ipc_led_gpio_table_extra = { + .dev_id = NULL, /* Filled during initialization */ + .table = { + GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 56, NULL, 6, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 59, NULL, 7, GPIO_ACTIVE_HIGH), + {} /* Terminating entry */ + }, +}; + +static int simatic_ipc_leds_gpio_apollolake_probe(struct platform_device *pdev) +{ + return simatic_ipc_leds_gpio_probe(pdev, &simatic_ipc_led_gpio_table, + &simatic_ipc_led_gpio_table_extra); +} + +static int simatic_ipc_leds_gpio_apollolake_remove(struct platform_device *pdev) +{ + return simatic_ipc_leds_gpio_remove(pdev, &simatic_ipc_led_gpio_table, + &simatic_ipc_led_gpio_table_extra); +} + +static struct platform_driver simatic_ipc_led_gpio_apollolake_driver = { + .probe = simatic_ipc_leds_gpio_apollolake_probe, + .remove = simatic_ipc_leds_gpio_apollolake_remove, + .driver = { + .name = KBUILD_MODNAME, + }, +}; +module_platform_driver(simatic_ipc_led_gpio_apollolake_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" KBUILD_MODNAME); +MODULE_SOFTDEP("pre: simatic-ipc-leds-gpio-core platform:apollolake-pinctrl"); +MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>"); diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio-core.c b/drivers/leds/simple/simatic-ipc-leds-gpio-core.c new file mode 100644 index 000000000000..2a21b663df87 --- /dev/null +++ b/drivers/leds/simple/simatic-ipc-leds-gpio-core.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Siemens SIMATIC IPC driver for GPIO based LEDs + * + * Copyright (c) Siemens AG, 2023 + * + * Author: + * Henning Schild <henning.schild@siemens.com> + */ + +#include <linux/gpio/machine.h> +#include <linux/gpio/consumer.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/platform_data/x86/simatic-ipc-base.h> + +#include "simatic-ipc-leds-gpio.h" + +static struct platform_device *simatic_leds_pdev; + +static const struct gpio_led simatic_ipc_gpio_leds[] = { + { .name = "red:" LED_FUNCTION_STATUS "-1" }, + { .name = "green:" LED_FUNCTION_STATUS "-1" }, + { .name = "red:" LED_FUNCTION_STATUS "-2" }, + { .name = "green:" LED_FUNCTION_STATUS "-2" }, + { .name = "red:" LED_FUNCTION_STATUS "-3" }, + { .name = "green:" LED_FUNCTION_STATUS "-3" }, +}; + +static const struct gpio_led_platform_data simatic_ipc_gpio_leds_pdata = { + .num_leds = ARRAY_SIZE(simatic_ipc_gpio_leds), + .leds = simatic_ipc_gpio_leds, +}; + +int simatic_ipc_leds_gpio_remove(struct platform_device *pdev, + struct gpiod_lookup_table *table, + struct gpiod_lookup_table *table_extra) +{ + gpiod_remove_lookup_table(table); + gpiod_remove_lookup_table(table_extra); + platform_device_unregister(simatic_leds_pdev); + + return 0; +} +EXPORT_SYMBOL_GPL(simatic_ipc_leds_gpio_remove); + +int simatic_ipc_leds_gpio_probe(struct platform_device *pdev, + struct gpiod_lookup_table *table, + struct gpiod_lookup_table *table_extra) +{ + const struct simatic_ipc_platform *plat = pdev->dev.platform_data; + struct device *dev = &pdev->dev; + struct gpio_desc *gpiod; + int err; + + switch (plat->devmode) { + case SIMATIC_IPC_DEVICE_127E: + case SIMATIC_IPC_DEVICE_227G: + break; + default: + return -ENODEV; + } + + gpiod_add_lookup_table(table); + simatic_leds_pdev = platform_device_register_resndata(NULL, + "leds-gpio", PLATFORM_DEVID_NONE, NULL, 0, + &simatic_ipc_gpio_leds_pdata, + sizeof(simatic_ipc_gpio_leds_pdata)); + if (IS_ERR(simatic_leds_pdev)) { + err = PTR_ERR(simatic_leds_pdev); + goto out; + } + + table_extra->dev_id = dev_name(dev); + gpiod_add_lookup_table(table_extra); + + /* PM_BIOS_BOOT_N */ + gpiod = gpiod_get_index(dev, NULL, 6, GPIOD_OUT_LOW); + if (IS_ERR(gpiod)) { + err = PTR_ERR(gpiod); + goto out; + } + gpiod_put(gpiod); + + /* PM_WDT_OUT */ + gpiod = gpiod_get_index(dev, NULL, 7, GPIOD_OUT_LOW); + if (IS_ERR(gpiod)) { + err = PTR_ERR(gpiod); + goto out; + } + gpiod_put(gpiod); + + return 0; +out: + simatic_ipc_leds_gpio_remove(pdev, table, table_extra); + + return err; +} +EXPORT_SYMBOL_GPL(simatic_ipc_leds_gpio_probe); + +MODULE_LICENSE("GPL v2"); +MODULE_SOFTDEP("pre: platform:leds-gpio"); +MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>"); diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio-f7188x.c b/drivers/leds/simple/simatic-ipc-leds-gpio-f7188x.c new file mode 100644 index 000000000000..583a6b6c7c22 --- /dev/null +++ b/drivers/leds/simple/simatic-ipc-leds-gpio-f7188x.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Siemens SIMATIC IPC driver for GPIO based LEDs + * + * Copyright (c) Siemens AG, 2023 + * + * Author: + * Henning Schild <henning.schild@siemens.com> + */ + +#include <linux/gpio/machine.h> +#include <linux/gpio/consumer.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/platform_data/x86/simatic-ipc-base.h> + +#include "simatic-ipc-leds-gpio.h" + +static struct gpiod_lookup_table simatic_ipc_led_gpio_table = { + .dev_id = "leds-gpio", + .table = { + GPIO_LOOKUP_IDX("gpio-f7188x-2", 0, NULL, 0, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("gpio-f7188x-2", 1, NULL, 1, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("gpio-f7188x-2", 2, NULL, 2, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("gpio-f7188x-2", 3, NULL, 3, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("gpio-f7188x-2", 4, NULL, 4, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("gpio-f7188x-2", 5, NULL, 5, GPIO_ACTIVE_LOW), + {} /* Terminating entry */ + }, +}; + +static struct gpiod_lookup_table simatic_ipc_led_gpio_table_extra = { + .dev_id = NULL, /* Filled during initialization */ + .table = { + GPIO_LOOKUP_IDX("gpio-f7188x-3", 6, NULL, 6, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("gpio-f7188x-3", 7, NULL, 7, GPIO_ACTIVE_HIGH), + {} /* Terminating entry */ + }, +}; + +static int simatic_ipc_leds_gpio_f7188x_probe(struct platform_device *pdev) +{ + return simatic_ipc_leds_gpio_probe(pdev, &simatic_ipc_led_gpio_table, + &simatic_ipc_led_gpio_table_extra); +} + +static int simatic_ipc_leds_gpio_f7188x_remove(struct platform_device *pdev) +{ + return simatic_ipc_leds_gpio_remove(pdev, &simatic_ipc_led_gpio_table, + &simatic_ipc_led_gpio_table_extra); +} + +static struct platform_driver simatic_ipc_led_gpio_driver = { + .probe = simatic_ipc_leds_gpio_f7188x_probe, + .remove = simatic_ipc_leds_gpio_f7188x_remove, + .driver = { + .name = KBUILD_MODNAME, + }, +}; +module_platform_driver(simatic_ipc_led_gpio_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" KBUILD_MODNAME); +MODULE_SOFTDEP("pre: simatic-ipc-leds-gpio-core gpio_f7188x"); +MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>"); diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio.c b/drivers/leds/simple/simatic-ipc-leds-gpio.c deleted file mode 100644 index e8d329b5a68c..000000000000 --- a/drivers/leds/simple/simatic-ipc-leds-gpio.c +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Siemens SIMATIC IPC driver for GPIO based LEDs - * - * Copyright (c) Siemens AG, 2022 - * - * Authors: - * Henning Schild <henning.schild@siemens.com> - */ - -#include <linux/gpio/machine.h> -#include <linux/gpio/consumer.h> -#include <linux/leds.h> -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/platform_data/x86/simatic-ipc-base.h> - -static struct gpiod_lookup_table *simatic_ipc_led_gpio_table; - -static struct gpiod_lookup_table simatic_ipc_led_gpio_table_127e = { - .dev_id = "leds-gpio", - .table = { - GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 52, NULL, 0, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 53, NULL, 1, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 57, NULL, 2, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 58, NULL, 3, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 60, NULL, 4, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 51, NULL, 5, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 56, NULL, 6, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 59, NULL, 7, GPIO_ACTIVE_HIGH), - }, -}; - -static struct gpiod_lookup_table simatic_ipc_led_gpio_table_227g = { - .dev_id = "leds-gpio", - .table = { - GPIO_LOOKUP_IDX("gpio-f7188x-2", 0, NULL, 0, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("gpio-f7188x-2", 1, NULL, 1, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("gpio-f7188x-2", 2, NULL, 2, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("gpio-f7188x-2", 3, NULL, 3, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("gpio-f7188x-2", 4, NULL, 4, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("gpio-f7188x-2", 5, NULL, 5, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("gpio-f7188x-3", 6, NULL, 6, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("gpio-f7188x-3", 7, NULL, 7, GPIO_ACTIVE_HIGH), - } -}; - -static const struct gpio_led simatic_ipc_gpio_leds[] = { - { .name = "red:" LED_FUNCTION_STATUS "-1" }, - { .name = "green:" LED_FUNCTION_STATUS "-1" }, - { .name = "red:" LED_FUNCTION_STATUS "-2" }, - { .name = "green:" LED_FUNCTION_STATUS "-2" }, - { .name = "red:" LED_FUNCTION_STATUS "-3" }, - { .name = "green:" LED_FUNCTION_STATUS "-3" }, -}; - -static const struct gpio_led_platform_data simatic_ipc_gpio_leds_pdata = { - .num_leds = ARRAY_SIZE(simatic_ipc_gpio_leds), - .leds = simatic_ipc_gpio_leds, -}; - -static struct platform_device *simatic_leds_pdev; - -static int simatic_ipc_leds_gpio_remove(struct platform_device *pdev) -{ - gpiod_remove_lookup_table(simatic_ipc_led_gpio_table); - platform_device_unregister(simatic_leds_pdev); - - return 0; -} - -static int simatic_ipc_leds_gpio_probe(struct platform_device *pdev) -{ - const struct simatic_ipc_platform *plat = pdev->dev.platform_data; - struct gpio_desc *gpiod; - int err; - - switch (plat->devmode) { - case SIMATIC_IPC_DEVICE_127E: - if (!IS_ENABLED(CONFIG_PINCTRL_BROXTON)) - return -ENODEV; - simatic_ipc_led_gpio_table = &simatic_ipc_led_gpio_table_127e; - break; - case SIMATIC_IPC_DEVICE_227G: - if (!IS_ENABLED(CONFIG_GPIO_F7188X)) - return -ENODEV; - request_module("gpio-f7188x"); - simatic_ipc_led_gpio_table = &simatic_ipc_led_gpio_table_227g; - break; - default: - return -ENODEV; - } - - gpiod_add_lookup_table(simatic_ipc_led_gpio_table); - simatic_leds_pdev = platform_device_register_resndata(NULL, - "leds-gpio", PLATFORM_DEVID_NONE, NULL, 0, - &simatic_ipc_gpio_leds_pdata, - sizeof(simatic_ipc_gpio_leds_pdata)); - if (IS_ERR(simatic_leds_pdev)) { - err = PTR_ERR(simatic_leds_pdev); - goto out; - } - - /* PM_BIOS_BOOT_N */ - gpiod = gpiod_get_index(&simatic_leds_pdev->dev, NULL, 6, GPIOD_OUT_LOW); - if (IS_ERR(gpiod)) { - err = PTR_ERR(gpiod); - goto out; - } - gpiod_put(gpiod); - - /* PM_WDT_OUT */ - gpiod = gpiod_get_index(&simatic_leds_pdev->dev, NULL, 7, GPIOD_OUT_LOW); - if (IS_ERR(gpiod)) { - err = PTR_ERR(gpiod); - goto out; - } - gpiod_put(gpiod); - - return 0; -out: - simatic_ipc_leds_gpio_remove(pdev); - - return err; -} - -static struct platform_driver simatic_ipc_led_gpio_driver = { - .probe = simatic_ipc_leds_gpio_probe, - .remove = simatic_ipc_leds_gpio_remove, - .driver = { - .name = KBUILD_MODNAME, - } -}; -module_platform_driver(simatic_ipc_led_gpio_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:" KBUILD_MODNAME); -MODULE_SOFTDEP("pre: platform:leds-gpio"); -MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>"); diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio.h b/drivers/leds/simple/simatic-ipc-leds-gpio.h new file mode 100644 index 000000000000..bf258c32f83d --- /dev/null +++ b/drivers/leds/simple/simatic-ipc-leds-gpio.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Siemens SIMATIC IPC driver for GPIO based LEDs + * + * Copyright (c) Siemens AG, 2023 + * + * Author: + * Henning Schild <henning.schild@siemens.com> + */ + +#ifndef _SIMATIC_IPC_LEDS_GPIO_H +#define _SIMATIC_IPC_LEDS_GPIO_H + +int simatic_ipc_leds_gpio_probe(struct platform_device *pdev, + struct gpiod_lookup_table *table, + struct gpiod_lookup_table *table_extra); + +int simatic_ipc_leds_gpio_remove(struct platform_device *pdev, + struct gpiod_lookup_table *table, + struct gpiod_lookup_table *table_extra); + +#endif /* _SIMATIC_IPC_LEDS_GPIO_H */ diff --git a/drivers/leds/simple/simatic-ipc-leds.c b/drivers/leds/simple/simatic-ipc-leds.c index 4894c228c165..2124f6d09930 100644 --- a/drivers/leds/simple/simatic-ipc-leds.c +++ b/drivers/leds/simple/simatic-ipc-leds.c @@ -126,7 +126,6 @@ static struct platform_driver simatic_ipc_led_driver = { .name = KBUILD_MODNAME, } }; - module_platform_driver(simatic_ipc_led_driver); MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/trigger/ledtrig-disk.c b/drivers/leds/trigger/ledtrig-disk.c index 0b7dfbd04273..e9b87ee944f2 100644 --- a/drivers/leds/trigger/ledtrig-disk.c +++ b/drivers/leds/trigger/ledtrig-disk.c @@ -19,16 +19,13 @@ DEFINE_LED_TRIGGER(ledtrig_disk_write); void ledtrig_disk_activity(bool write) { - unsigned long blink_delay = BLINK_DELAY; - - led_trigger_blink_oneshot(ledtrig_disk, - &blink_delay, &blink_delay, 0); + led_trigger_blink_oneshot(ledtrig_disk, BLINK_DELAY, BLINK_DELAY, 0); if (write) led_trigger_blink_oneshot(ledtrig_disk_write, - &blink_delay, &blink_delay, 0); + BLINK_DELAY, BLINK_DELAY, 0); else led_trigger_blink_oneshot(ledtrig_disk_read, - &blink_delay, &blink_delay, 0); + BLINK_DELAY, BLINK_DELAY, 0); } EXPORT_SYMBOL(ledtrig_disk_activity); diff --git a/drivers/leds/trigger/ledtrig-mtd.c b/drivers/leds/trigger/ledtrig-mtd.c index 8fa763c2269b..bbe6876a249d 100644 --- a/drivers/leds/trigger/ledtrig-mtd.c +++ b/drivers/leds/trigger/ledtrig-mtd.c @@ -22,12 +22,8 @@ DEFINE_LED_TRIGGER(ledtrig_nand); void ledtrig_mtd_activity(void) { - unsigned long blink_delay = BLINK_DELAY; - - led_trigger_blink_oneshot(ledtrig_mtd, - &blink_delay, &blink_delay, 0); - led_trigger_blink_oneshot(ledtrig_nand, - &blink_delay, &blink_delay, 0); + led_trigger_blink_oneshot(ledtrig_mtd, BLINK_DELAY, BLINK_DELAY, 0); + led_trigger_blink_oneshot(ledtrig_nand, BLINK_DELAY, BLINK_DELAY, 0); } EXPORT_SYMBOL(ledtrig_mtd_activity); diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c index 32b66703068a..c9bc5a91ec83 100644 --- a/drivers/leds/trigger/ledtrig-netdev.c +++ b/drivers/leds/trigger/ledtrig-netdev.c @@ -462,8 +462,7 @@ static int netdev_trig_notify(struct notifier_block *nb, get_device_state(trigger_data); fallthrough; case NETDEV_REGISTER: - if (trigger_data->net_dev) - dev_put(trigger_data->net_dev); + dev_put(trigger_data->net_dev); dev_hold(dev); trigger_data->net_dev = dev; break; @@ -594,8 +593,7 @@ static void netdev_trig_deactivate(struct led_classdev *led_cdev) cancel_delayed_work_sync(&trigger_data->work); - if (trigger_data->net_dev) - dev_put(trigger_data->net_dev); + dev_put(trigger_data->net_dev); kfree(trigger_data); } |