Loading Documentation/devicetree/bindings/leds/leds-qti-tri-led.txt 0 → 100644 +60 −0 Original line number Diff line number Diff line Qualcomm Technologies, Inc. TRI_LED driver specific bindings This binding document describes the properties of TRI_LED module in Qualcomm Technologies, Inc. PMIC chips. - compatible: Usage: required Value type: <string> Definition: Must be "qcom,tri-led". - reg: Usage: required Value type: <prop-encoded-array> Definition: Register base of the TRI_LED module and length. Properties for child nodes: - pwms: Usage: required Value type: <prop-encoded-array> Definition: The PWM device (phandle) used for controlling LED. - led-sources: Usage: required Value type: <prop-encoded-array> Definition: see Documentation/devicetree/bindings/leds/common.txt; Device current output identifiers are: 0 - LED1_EN, 1 - LED2_EN, 2 - LED3_EN. - label: Usage: optional Value type: <string> Definition: see Documentation/devicetree/bindings/leds/common.txt; - linux,default-trigger: Usage: optional Value_type: <string> Definition: see Documentation/devicetree/bindings/leds/common.txt; Example: pmi8998_rgb: tri-led@d000{ compatible = "qcom,tri-led"; reg = <0xd000 0x100>; red { label = "red"; pwms = <&pmi8998_lpg 4 1000000>; led-sources = <0>; }; green { label = "green"; pwms = <&pmi8998_lpg 3 1000000>; led-sources = <1>; }; blue { label = "blue"; pwms = <&pmi8998_lpg 2 1000000>; led-sources = <2>; }; }; Documentation/devicetree/bindings/pwm/pwm-qti-lpg.txt 0 → 100644 +36 −0 Original line number Diff line number Diff line Qualcomm Technologies, Inc. LPG driver specific bindings This binding document describes the properties of LPG (Light Pulse Generator) device module in Qualcomm Technologies, Inc. PMIC chips. - compatible: Usage: required Value type: <string> Definition: Must be "qcom,pwm-lpg". - reg: Usage: required Value type: <prop-encoded-array> Definition: Register base and length for LPG modules. The length varies based on the number of channels available in the PMIC chips. - reg-names: Usage: required Value type: <string> Definition: The name of the register defined in the reg property. It must be "lpg-base". - #pwm-cells: Usage: required Value type: <u32> Definition: See Documentation/devicetree/bindings/pwm/pwm.txt; Example: pmi8998_lpg: lpg@b100 { compatible = "qcom,pwm-lpg"; reg = <0xb100 0x600>; reg-names = "lpg-base"; #pwm-cells = <2>; }; drivers/leds/Kconfig +9 −0 Original line number Diff line number Diff line Loading @@ -662,6 +662,15 @@ config LEDS_POWERNV To compile this driver as a module, choose 'm' here: the module will be called leds-powernv. config LEDS_QTI_TRI_LED tristate "LED support for Qualcomm Technologies, Inc. TRI_LED" depends on LEDS_CLASS && MFD_SPMI_PMIC && PWM && OF help This driver supports the TRI_LED module found in Qualcomm Technologies, Inc. PMIC chips. TRI_LED supports 3 LED drivers at max and each is controlled by a PWM channel used for dimming or blinking. config LEDS_SYSCON bool "LED support for LEDs on system controllers" depends on LEDS_CLASS=y Loading drivers/leds/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -64,6 +64,7 @@ obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o obj-$(CONFIG_LEDS_QTI_TRI_LED) += leds-qti-tri-led.o obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o obj-$(CONFIG_LEDS_KTD2692) += leds-ktd2692.o Loading drivers/leds/leds-qti-tri-led.c 0 → 100644 +512 −0 Original line number Diff line number Diff line /* Copyright (c) 2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include <linux/bitops.h> #include <linux/device.h> #include <linux/err.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/leds.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/platform_device.h> #include <linux/pwm.h> #include <linux/regmap.h> #include <linux/types.h> #define TRILED_REG_TYPE 0x04 #define TRILED_REG_SUBTYPE 0x05 #define TRILED_REG_EN_CTL 0x46 /* TRILED_REG_EN_CTL */ #define TRILED_EN_CTL_MASK GENMASK(7, 5) #define TRILED_EN_CTL_MAX_BIT 7 #define TRILED_TYPE 0x19 #define TRILED_SUBTYPE_LED3H0L12 0x02 #define TRILED_SUBTYPE_LED2H0L12 0x03 #define TRILED_SUBTYPE_LED1H2L12 0x04 #define TRILED_NUM_MAX 3 #define PWM_PERIOD_DEFAULT_NS 1000000 #define LED_BLINK_ON_MS 125 #define LED_BLINK_OFF_MS 875 struct pwm_setting { u32 pre_period_ns; u32 period_ns; u32 duty_ns; }; struct led_setting { u32 on_ms; u32 off_ms; enum led_brightness brightness; bool blink; }; struct qpnp_led_dev { struct led_classdev cdev; struct pwm_device *pwm_dev; struct pwm_setting pwm_setting; struct led_setting led_setting; struct qpnp_tri_led_chip *chip; struct mutex lock; const char *label; const char *default_trigger; u8 id; bool blinking; }; struct qpnp_tri_led_chip { struct device *dev; struct regmap *regmap; struct qpnp_led_dev *leds; struct mutex bus_lock; int num_leds; u16 reg_base; u8 subtype; }; static int qpnp_tri_led_read(struct qpnp_tri_led_chip *chip, u16 addr, u8 *val) { int rc; unsigned int tmp; mutex_lock(&chip->bus_lock); rc = regmap_read(chip->regmap, chip->reg_base + addr, &tmp); if (rc < 0) dev_err(chip->dev, "Read addr 0x%x failed, rc=%d\n", addr, rc); else *val = (u8)tmp; mutex_unlock(&chip->bus_lock); return rc; } static int qpnp_tri_led_masked_write(struct qpnp_tri_led_chip *chip, u16 addr, u8 mask, u8 val) { int rc; mutex_lock(&chip->bus_lock); rc = regmap_update_bits(chip->regmap, chip->reg_base + addr, mask, val); if (rc < 0) dev_err(chip->dev, "Update addr 0x%x to val 0x%x with mask 0x%x failed, rc=%d\n", addr, val, mask, rc); mutex_unlock(&chip->bus_lock); return rc; } static int __tri_led_config_pwm(struct qpnp_led_dev *led, struct pwm_setting *pwm) { struct pwm_state pstate; int rc; pwm_get_state(led->pwm_dev, &pstate); pstate.enabled = !!(pwm->duty_ns != 0); pstate.period = pwm->period_ns; pstate.duty_cycle = pwm->duty_ns; rc = pwm_apply_state(led->pwm_dev, &pstate); if (rc < 0) dev_err(led->chip->dev, "Apply PWM state for %s led failed, rc=%d\n", led->cdev.name, rc); return rc; } static int __tri_led_set(struct qpnp_led_dev *led) { int rc = 0; u8 val = 0, mask = 0; rc = __tri_led_config_pwm(led, &led->pwm_setting); if (rc < 0) { dev_err(led->chip->dev, "Configure PWM for %s led failed, rc=%d\n", led->cdev.name, rc); return rc; } mask |= 1 << (TRILED_EN_CTL_MAX_BIT - led->id); if (led->pwm_setting.duty_ns == 0) val = 0; else val = mask; rc = qpnp_tri_led_masked_write(led->chip, TRILED_REG_EN_CTL, mask, val); if (rc < 0) dev_err(led->chip->dev, "Update addr 0x%x failed, rc=%d\n", TRILED_REG_EN_CTL, rc); return rc; } static int qpnp_tri_led_set(struct qpnp_led_dev *led) { u32 on_ms, off_ms, period_ns, duty_ns; enum led_brightness brightness = led->led_setting.brightness; int rc = 0; if (led->led_setting.blink) { on_ms = led->led_setting.on_ms; off_ms = led->led_setting.off_ms; if (on_ms > INT_MAX / NSEC_PER_MSEC) duty_ns = INT_MAX - 1; else duty_ns = on_ms * NSEC_PER_MSEC; if (on_ms + off_ms > INT_MAX / NSEC_PER_MSEC) { period_ns = INT_MAX; duty_ns = (period_ns / (on_ms + off_ms)) * on_ms; } else { period_ns = (on_ms + off_ms) * NSEC_PER_MSEC; } if (period_ns < duty_ns && duty_ns != 0) period_ns = duty_ns + 1; } else { /* Use initial period if no blinking is required */ period_ns = led->pwm_setting.pre_period_ns; if (period_ns > INT_MAX / brightness) duty_ns = (period_ns / LED_FULL) * brightness; else duty_ns = (period_ns * brightness) / LED_FULL; if (period_ns < duty_ns && duty_ns != 0) period_ns = duty_ns + 1; } dev_dbg(led->chip->dev, "PWM settings for %s led: period = %dns, duty = %dns\n", led->cdev.name, period_ns, duty_ns); led->pwm_setting.duty_ns = duty_ns; led->pwm_setting.period_ns = period_ns; rc = __tri_led_set(led); if (rc < 0) { dev_err(led->chip->dev, "__tri_led_set %s failed, rc=%d\n", led->cdev.name, rc); return rc; } if (led->led_setting.blink) { led->cdev.brightness = LED_FULL; led->blinking = true; } else { led->cdev.brightness = led->led_setting.brightness; led->blinking = false; } return rc; } static int qpnp_tri_led_set_brightness(struct led_classdev *led_cdev, enum led_brightness brightness) { struct qpnp_led_dev *led = container_of(led_cdev, struct qpnp_led_dev, cdev); int rc = 0; mutex_lock(&led->lock); if (brightness > LED_FULL) brightness = LED_FULL; if (brightness == led->led_setting.brightness && !led->blinking) { mutex_unlock(&led->lock); return 0; } led->led_setting.brightness = brightness; if (!!brightness) led->led_setting.off_ms = 0; else led->led_setting.on_ms = 0; led->led_setting.blink = false; rc = qpnp_tri_led_set(led); if (rc) dev_err(led->chip->dev, "Set led failed for %s, rc=%d\n", led->label, rc); mutex_unlock(&led->lock); return rc; } static enum led_brightness qpnp_tri_led_get_brightness( struct led_classdev *led_cdev) { return led_cdev->brightness; } static int qpnp_tri_led_set_blink(struct led_classdev *led_cdev, unsigned long *on_ms, unsigned long *off_ms) { struct qpnp_led_dev *led = container_of(led_cdev, struct qpnp_led_dev, cdev); int rc = 0; mutex_lock(&led->lock); if (led->blinking && *on_ms == led->led_setting.on_ms && *off_ms == led->led_setting.off_ms) { dev_dbg(led_cdev->dev, "Ignore, on/off setting is not changed: on %lums, off %lums\n", *on_ms, *off_ms); mutex_unlock(&led->lock); return 0; } if (*on_ms == 0) { led->led_setting.blink = false; led->led_setting.brightness = LED_OFF; } else if (*off_ms == 0) { led->led_setting.blink = false; led->led_setting.brightness = led->cdev.brightness; } else { led->led_setting.on_ms = *on_ms; led->led_setting.off_ms = *off_ms; led->led_setting.blink = true; } rc = qpnp_tri_led_set(led); if (rc) dev_err(led->chip->dev, "Set led failed for %s, rc=%d\n", led->label, rc); mutex_unlock(&led->lock); return rc; } static int qpnp_tri_led_register(struct qpnp_tri_led_chip *chip) { struct qpnp_led_dev *led; int rc, i, j; for (i = 0; i < chip->num_leds; i++) { led = &chip->leds[i]; mutex_init(&led->lock); led->cdev.name = led->label; led->cdev.max_brightness = LED_FULL; led->cdev.brightness_set_blocking = qpnp_tri_led_set_brightness; led->cdev.brightness_get = qpnp_tri_led_get_brightness; led->cdev.blink_set = qpnp_tri_led_set_blink; led->cdev.default_trigger = led->default_trigger; led->cdev.brightness = LED_OFF; led->cdev.blink_delay_on = LED_BLINK_ON_MS; led->cdev.blink_delay_off = LED_BLINK_OFF_MS; rc = devm_led_classdev_register(chip->dev, &led->cdev); if (rc < 0) { dev_err(chip->dev, "%s led class device registering failed, rc=%d\n", led->label, rc); goto destroy; } } return 0; destroy: for (j = 0; j <= i; j++) mutex_destroy(&chip->leds[i].lock); return rc; } static int qpnp_tri_led_hw_init(struct qpnp_tri_led_chip *chip) { int rc = 0; u8 val; rc = qpnp_tri_led_read(chip, TRILED_REG_TYPE, &val); if (rc < 0) { dev_err(chip->dev, "Read REG_TYPE failed, rc=%d\n", rc); return rc; } if (val != TRILED_TYPE) { dev_err(chip->dev, "invalid subtype(%d)\n", val); return -ENODEV; } rc = qpnp_tri_led_read(chip, TRILED_REG_SUBTYPE, &val); if (rc < 0) { dev_err(chip->dev, "Read REG_SUBTYPE failed, rc=%d\n", rc); return rc; } chip->subtype = val; return 0; } static int qpnp_tri_led_parse_dt(struct qpnp_tri_led_chip *chip) { struct device_node *node = chip->dev->of_node, *child_node; struct qpnp_led_dev *led; struct pwm_args pargs; const __be32 *addr; int rc, id, i = 0; addr = of_get_address(chip->dev->of_node, 0, NULL, NULL); if (!addr) { dev_err(chip->dev, "Getting address failed\n"); return -EINVAL; } chip->reg_base = be32_to_cpu(addr[0]); chip->num_leds = of_get_available_child_count(node); if (chip->num_leds == 0) { dev_err(chip->dev, "No led child node defined\n"); return -ENODEV; } if (chip->num_leds > TRILED_NUM_MAX) { dev_err(chip->dev, "can't support %d leds(max %d)\n", chip->num_leds, TRILED_NUM_MAX); return -EINVAL; } chip->leds = devm_kcalloc(chip->dev, chip->num_leds, sizeof(struct qpnp_led_dev), GFP_KERNEL); if (!chip->leds) return -ENOMEM; for_each_available_child_of_node(node, child_node) { rc = of_property_read_u32(child_node, "led-sources", &id); if (rc) { dev_err(chip->dev, "Get led-sources failed, rc=%d\n", rc); return rc; } if (id >= TRILED_NUM_MAX) { dev_err(chip->dev, "only support 0~%d current source\n", TRILED_NUM_MAX - 1); return -EINVAL; } led = &chip->leds[i++]; led->chip = chip; led->id = id; led->label = of_get_property(child_node, "label", NULL) ? : child_node->name; led->pwm_dev = devm_of_pwm_get(chip->dev, child_node, NULL); if (IS_ERR(led->pwm_dev)) { rc = PTR_ERR(led->pwm_dev); if (rc != -EPROBE_DEFER) dev_err(chip->dev, "Get pwm device for %s led failed, rc=%d\n", led->label, rc); return rc; } pwm_get_args(led->pwm_dev, &pargs); if (pargs.period == 0) led->pwm_setting.pre_period_ns = PWM_PERIOD_DEFAULT_NS; else led->pwm_setting.pre_period_ns = pargs.period; led->default_trigger = of_get_property(child_node, "linux,default-trigger", NULL); } return rc; } static int qpnp_tri_led_probe(struct platform_device *pdev) { struct qpnp_tri_led_chip *chip; int rc = 0; chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; chip->dev = &pdev->dev; chip->regmap = dev_get_regmap(chip->dev->parent, NULL); if (!chip->regmap) { dev_err(chip->dev, "Getting regmap failed\n"); return -EINVAL; } rc = qpnp_tri_led_parse_dt(chip); if (rc < 0) { dev_err(chip->dev, "Devicetree properties parsing failed, rc=%d\n", rc); return rc; } mutex_init(&chip->bus_lock); rc = qpnp_tri_led_hw_init(chip); if (rc) { dev_err(chip->dev, "HW initialization failed, rc=%d\n", rc); goto destroy; } dev_set_drvdata(chip->dev, chip); rc = qpnp_tri_led_register(chip); if (rc < 0) { dev_err(chip->dev, "Registering LED class devices failed, rc=%d\n", rc); goto destroy; } dev_dbg(chip->dev, "Tri-led module with subtype 0x%x is detected\n", chip->subtype); return 0; destroy: mutex_destroy(&chip->bus_lock); dev_set_drvdata(chip->dev, NULL); return rc; } static int qpnp_tri_led_remove(struct platform_device *pdev) { int i; struct qpnp_tri_led_chip *chip = dev_get_drvdata(&pdev->dev); mutex_destroy(&chip->bus_lock); for (i = 0; i < chip->num_leds; i++) mutex_destroy(&chip->leds[i].lock); dev_set_drvdata(chip->dev, NULL); return 0; } static const struct of_device_id qpnp_tri_led_of_match[] = { { .compatible = "qcom,tri-led",}, { }, }; static struct platform_driver qpnp_tri_led_driver = { .driver = { .name = "qcom,tri-led", .of_match_table = qpnp_tri_led_of_match, }, .probe = qpnp_tri_led_probe, .remove = qpnp_tri_led_remove, }; module_platform_driver(qpnp_tri_led_driver); MODULE_DESCRIPTION("QTI TRI_LED driver"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("leds:qpnp-tri-led"); Loading
Documentation/devicetree/bindings/leds/leds-qti-tri-led.txt 0 → 100644 +60 −0 Original line number Diff line number Diff line Qualcomm Technologies, Inc. TRI_LED driver specific bindings This binding document describes the properties of TRI_LED module in Qualcomm Technologies, Inc. PMIC chips. - compatible: Usage: required Value type: <string> Definition: Must be "qcom,tri-led". - reg: Usage: required Value type: <prop-encoded-array> Definition: Register base of the TRI_LED module and length. Properties for child nodes: - pwms: Usage: required Value type: <prop-encoded-array> Definition: The PWM device (phandle) used for controlling LED. - led-sources: Usage: required Value type: <prop-encoded-array> Definition: see Documentation/devicetree/bindings/leds/common.txt; Device current output identifiers are: 0 - LED1_EN, 1 - LED2_EN, 2 - LED3_EN. - label: Usage: optional Value type: <string> Definition: see Documentation/devicetree/bindings/leds/common.txt; - linux,default-trigger: Usage: optional Value_type: <string> Definition: see Documentation/devicetree/bindings/leds/common.txt; Example: pmi8998_rgb: tri-led@d000{ compatible = "qcom,tri-led"; reg = <0xd000 0x100>; red { label = "red"; pwms = <&pmi8998_lpg 4 1000000>; led-sources = <0>; }; green { label = "green"; pwms = <&pmi8998_lpg 3 1000000>; led-sources = <1>; }; blue { label = "blue"; pwms = <&pmi8998_lpg 2 1000000>; led-sources = <2>; }; };
Documentation/devicetree/bindings/pwm/pwm-qti-lpg.txt 0 → 100644 +36 −0 Original line number Diff line number Diff line Qualcomm Technologies, Inc. LPG driver specific bindings This binding document describes the properties of LPG (Light Pulse Generator) device module in Qualcomm Technologies, Inc. PMIC chips. - compatible: Usage: required Value type: <string> Definition: Must be "qcom,pwm-lpg". - reg: Usage: required Value type: <prop-encoded-array> Definition: Register base and length for LPG modules. The length varies based on the number of channels available in the PMIC chips. - reg-names: Usage: required Value type: <string> Definition: The name of the register defined in the reg property. It must be "lpg-base". - #pwm-cells: Usage: required Value type: <u32> Definition: See Documentation/devicetree/bindings/pwm/pwm.txt; Example: pmi8998_lpg: lpg@b100 { compatible = "qcom,pwm-lpg"; reg = <0xb100 0x600>; reg-names = "lpg-base"; #pwm-cells = <2>; };
drivers/leds/Kconfig +9 −0 Original line number Diff line number Diff line Loading @@ -662,6 +662,15 @@ config LEDS_POWERNV To compile this driver as a module, choose 'm' here: the module will be called leds-powernv. config LEDS_QTI_TRI_LED tristate "LED support for Qualcomm Technologies, Inc. TRI_LED" depends on LEDS_CLASS && MFD_SPMI_PMIC && PWM && OF help This driver supports the TRI_LED module found in Qualcomm Technologies, Inc. PMIC chips. TRI_LED supports 3 LED drivers at max and each is controlled by a PWM channel used for dimming or blinking. config LEDS_SYSCON bool "LED support for LEDs on system controllers" depends on LEDS_CLASS=y Loading
drivers/leds/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -64,6 +64,7 @@ obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o obj-$(CONFIG_LEDS_QTI_TRI_LED) += leds-qti-tri-led.o obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o obj-$(CONFIG_LEDS_KTD2692) += leds-ktd2692.o Loading
drivers/leds/leds-qti-tri-led.c 0 → 100644 +512 −0 Original line number Diff line number Diff line /* Copyright (c) 2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include <linux/bitops.h> #include <linux/device.h> #include <linux/err.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/leds.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/platform_device.h> #include <linux/pwm.h> #include <linux/regmap.h> #include <linux/types.h> #define TRILED_REG_TYPE 0x04 #define TRILED_REG_SUBTYPE 0x05 #define TRILED_REG_EN_CTL 0x46 /* TRILED_REG_EN_CTL */ #define TRILED_EN_CTL_MASK GENMASK(7, 5) #define TRILED_EN_CTL_MAX_BIT 7 #define TRILED_TYPE 0x19 #define TRILED_SUBTYPE_LED3H0L12 0x02 #define TRILED_SUBTYPE_LED2H0L12 0x03 #define TRILED_SUBTYPE_LED1H2L12 0x04 #define TRILED_NUM_MAX 3 #define PWM_PERIOD_DEFAULT_NS 1000000 #define LED_BLINK_ON_MS 125 #define LED_BLINK_OFF_MS 875 struct pwm_setting { u32 pre_period_ns; u32 period_ns; u32 duty_ns; }; struct led_setting { u32 on_ms; u32 off_ms; enum led_brightness brightness; bool blink; }; struct qpnp_led_dev { struct led_classdev cdev; struct pwm_device *pwm_dev; struct pwm_setting pwm_setting; struct led_setting led_setting; struct qpnp_tri_led_chip *chip; struct mutex lock; const char *label; const char *default_trigger; u8 id; bool blinking; }; struct qpnp_tri_led_chip { struct device *dev; struct regmap *regmap; struct qpnp_led_dev *leds; struct mutex bus_lock; int num_leds; u16 reg_base; u8 subtype; }; static int qpnp_tri_led_read(struct qpnp_tri_led_chip *chip, u16 addr, u8 *val) { int rc; unsigned int tmp; mutex_lock(&chip->bus_lock); rc = regmap_read(chip->regmap, chip->reg_base + addr, &tmp); if (rc < 0) dev_err(chip->dev, "Read addr 0x%x failed, rc=%d\n", addr, rc); else *val = (u8)tmp; mutex_unlock(&chip->bus_lock); return rc; } static int qpnp_tri_led_masked_write(struct qpnp_tri_led_chip *chip, u16 addr, u8 mask, u8 val) { int rc; mutex_lock(&chip->bus_lock); rc = regmap_update_bits(chip->regmap, chip->reg_base + addr, mask, val); if (rc < 0) dev_err(chip->dev, "Update addr 0x%x to val 0x%x with mask 0x%x failed, rc=%d\n", addr, val, mask, rc); mutex_unlock(&chip->bus_lock); return rc; } static int __tri_led_config_pwm(struct qpnp_led_dev *led, struct pwm_setting *pwm) { struct pwm_state pstate; int rc; pwm_get_state(led->pwm_dev, &pstate); pstate.enabled = !!(pwm->duty_ns != 0); pstate.period = pwm->period_ns; pstate.duty_cycle = pwm->duty_ns; rc = pwm_apply_state(led->pwm_dev, &pstate); if (rc < 0) dev_err(led->chip->dev, "Apply PWM state for %s led failed, rc=%d\n", led->cdev.name, rc); return rc; } static int __tri_led_set(struct qpnp_led_dev *led) { int rc = 0; u8 val = 0, mask = 0; rc = __tri_led_config_pwm(led, &led->pwm_setting); if (rc < 0) { dev_err(led->chip->dev, "Configure PWM for %s led failed, rc=%d\n", led->cdev.name, rc); return rc; } mask |= 1 << (TRILED_EN_CTL_MAX_BIT - led->id); if (led->pwm_setting.duty_ns == 0) val = 0; else val = mask; rc = qpnp_tri_led_masked_write(led->chip, TRILED_REG_EN_CTL, mask, val); if (rc < 0) dev_err(led->chip->dev, "Update addr 0x%x failed, rc=%d\n", TRILED_REG_EN_CTL, rc); return rc; } static int qpnp_tri_led_set(struct qpnp_led_dev *led) { u32 on_ms, off_ms, period_ns, duty_ns; enum led_brightness brightness = led->led_setting.brightness; int rc = 0; if (led->led_setting.blink) { on_ms = led->led_setting.on_ms; off_ms = led->led_setting.off_ms; if (on_ms > INT_MAX / NSEC_PER_MSEC) duty_ns = INT_MAX - 1; else duty_ns = on_ms * NSEC_PER_MSEC; if (on_ms + off_ms > INT_MAX / NSEC_PER_MSEC) { period_ns = INT_MAX; duty_ns = (period_ns / (on_ms + off_ms)) * on_ms; } else { period_ns = (on_ms + off_ms) * NSEC_PER_MSEC; } if (period_ns < duty_ns && duty_ns != 0) period_ns = duty_ns + 1; } else { /* Use initial period if no blinking is required */ period_ns = led->pwm_setting.pre_period_ns; if (period_ns > INT_MAX / brightness) duty_ns = (period_ns / LED_FULL) * brightness; else duty_ns = (period_ns * brightness) / LED_FULL; if (period_ns < duty_ns && duty_ns != 0) period_ns = duty_ns + 1; } dev_dbg(led->chip->dev, "PWM settings for %s led: period = %dns, duty = %dns\n", led->cdev.name, period_ns, duty_ns); led->pwm_setting.duty_ns = duty_ns; led->pwm_setting.period_ns = period_ns; rc = __tri_led_set(led); if (rc < 0) { dev_err(led->chip->dev, "__tri_led_set %s failed, rc=%d\n", led->cdev.name, rc); return rc; } if (led->led_setting.blink) { led->cdev.brightness = LED_FULL; led->blinking = true; } else { led->cdev.brightness = led->led_setting.brightness; led->blinking = false; } return rc; } static int qpnp_tri_led_set_brightness(struct led_classdev *led_cdev, enum led_brightness brightness) { struct qpnp_led_dev *led = container_of(led_cdev, struct qpnp_led_dev, cdev); int rc = 0; mutex_lock(&led->lock); if (brightness > LED_FULL) brightness = LED_FULL; if (brightness == led->led_setting.brightness && !led->blinking) { mutex_unlock(&led->lock); return 0; } led->led_setting.brightness = brightness; if (!!brightness) led->led_setting.off_ms = 0; else led->led_setting.on_ms = 0; led->led_setting.blink = false; rc = qpnp_tri_led_set(led); if (rc) dev_err(led->chip->dev, "Set led failed for %s, rc=%d\n", led->label, rc); mutex_unlock(&led->lock); return rc; } static enum led_brightness qpnp_tri_led_get_brightness( struct led_classdev *led_cdev) { return led_cdev->brightness; } static int qpnp_tri_led_set_blink(struct led_classdev *led_cdev, unsigned long *on_ms, unsigned long *off_ms) { struct qpnp_led_dev *led = container_of(led_cdev, struct qpnp_led_dev, cdev); int rc = 0; mutex_lock(&led->lock); if (led->blinking && *on_ms == led->led_setting.on_ms && *off_ms == led->led_setting.off_ms) { dev_dbg(led_cdev->dev, "Ignore, on/off setting is not changed: on %lums, off %lums\n", *on_ms, *off_ms); mutex_unlock(&led->lock); return 0; } if (*on_ms == 0) { led->led_setting.blink = false; led->led_setting.brightness = LED_OFF; } else if (*off_ms == 0) { led->led_setting.blink = false; led->led_setting.brightness = led->cdev.brightness; } else { led->led_setting.on_ms = *on_ms; led->led_setting.off_ms = *off_ms; led->led_setting.blink = true; } rc = qpnp_tri_led_set(led); if (rc) dev_err(led->chip->dev, "Set led failed for %s, rc=%d\n", led->label, rc); mutex_unlock(&led->lock); return rc; } static int qpnp_tri_led_register(struct qpnp_tri_led_chip *chip) { struct qpnp_led_dev *led; int rc, i, j; for (i = 0; i < chip->num_leds; i++) { led = &chip->leds[i]; mutex_init(&led->lock); led->cdev.name = led->label; led->cdev.max_brightness = LED_FULL; led->cdev.brightness_set_blocking = qpnp_tri_led_set_brightness; led->cdev.brightness_get = qpnp_tri_led_get_brightness; led->cdev.blink_set = qpnp_tri_led_set_blink; led->cdev.default_trigger = led->default_trigger; led->cdev.brightness = LED_OFF; led->cdev.blink_delay_on = LED_BLINK_ON_MS; led->cdev.blink_delay_off = LED_BLINK_OFF_MS; rc = devm_led_classdev_register(chip->dev, &led->cdev); if (rc < 0) { dev_err(chip->dev, "%s led class device registering failed, rc=%d\n", led->label, rc); goto destroy; } } return 0; destroy: for (j = 0; j <= i; j++) mutex_destroy(&chip->leds[i].lock); return rc; } static int qpnp_tri_led_hw_init(struct qpnp_tri_led_chip *chip) { int rc = 0; u8 val; rc = qpnp_tri_led_read(chip, TRILED_REG_TYPE, &val); if (rc < 0) { dev_err(chip->dev, "Read REG_TYPE failed, rc=%d\n", rc); return rc; } if (val != TRILED_TYPE) { dev_err(chip->dev, "invalid subtype(%d)\n", val); return -ENODEV; } rc = qpnp_tri_led_read(chip, TRILED_REG_SUBTYPE, &val); if (rc < 0) { dev_err(chip->dev, "Read REG_SUBTYPE failed, rc=%d\n", rc); return rc; } chip->subtype = val; return 0; } static int qpnp_tri_led_parse_dt(struct qpnp_tri_led_chip *chip) { struct device_node *node = chip->dev->of_node, *child_node; struct qpnp_led_dev *led; struct pwm_args pargs; const __be32 *addr; int rc, id, i = 0; addr = of_get_address(chip->dev->of_node, 0, NULL, NULL); if (!addr) { dev_err(chip->dev, "Getting address failed\n"); return -EINVAL; } chip->reg_base = be32_to_cpu(addr[0]); chip->num_leds = of_get_available_child_count(node); if (chip->num_leds == 0) { dev_err(chip->dev, "No led child node defined\n"); return -ENODEV; } if (chip->num_leds > TRILED_NUM_MAX) { dev_err(chip->dev, "can't support %d leds(max %d)\n", chip->num_leds, TRILED_NUM_MAX); return -EINVAL; } chip->leds = devm_kcalloc(chip->dev, chip->num_leds, sizeof(struct qpnp_led_dev), GFP_KERNEL); if (!chip->leds) return -ENOMEM; for_each_available_child_of_node(node, child_node) { rc = of_property_read_u32(child_node, "led-sources", &id); if (rc) { dev_err(chip->dev, "Get led-sources failed, rc=%d\n", rc); return rc; } if (id >= TRILED_NUM_MAX) { dev_err(chip->dev, "only support 0~%d current source\n", TRILED_NUM_MAX - 1); return -EINVAL; } led = &chip->leds[i++]; led->chip = chip; led->id = id; led->label = of_get_property(child_node, "label", NULL) ? : child_node->name; led->pwm_dev = devm_of_pwm_get(chip->dev, child_node, NULL); if (IS_ERR(led->pwm_dev)) { rc = PTR_ERR(led->pwm_dev); if (rc != -EPROBE_DEFER) dev_err(chip->dev, "Get pwm device for %s led failed, rc=%d\n", led->label, rc); return rc; } pwm_get_args(led->pwm_dev, &pargs); if (pargs.period == 0) led->pwm_setting.pre_period_ns = PWM_PERIOD_DEFAULT_NS; else led->pwm_setting.pre_period_ns = pargs.period; led->default_trigger = of_get_property(child_node, "linux,default-trigger", NULL); } return rc; } static int qpnp_tri_led_probe(struct platform_device *pdev) { struct qpnp_tri_led_chip *chip; int rc = 0; chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; chip->dev = &pdev->dev; chip->regmap = dev_get_regmap(chip->dev->parent, NULL); if (!chip->regmap) { dev_err(chip->dev, "Getting regmap failed\n"); return -EINVAL; } rc = qpnp_tri_led_parse_dt(chip); if (rc < 0) { dev_err(chip->dev, "Devicetree properties parsing failed, rc=%d\n", rc); return rc; } mutex_init(&chip->bus_lock); rc = qpnp_tri_led_hw_init(chip); if (rc) { dev_err(chip->dev, "HW initialization failed, rc=%d\n", rc); goto destroy; } dev_set_drvdata(chip->dev, chip); rc = qpnp_tri_led_register(chip); if (rc < 0) { dev_err(chip->dev, "Registering LED class devices failed, rc=%d\n", rc); goto destroy; } dev_dbg(chip->dev, "Tri-led module with subtype 0x%x is detected\n", chip->subtype); return 0; destroy: mutex_destroy(&chip->bus_lock); dev_set_drvdata(chip->dev, NULL); return rc; } static int qpnp_tri_led_remove(struct platform_device *pdev) { int i; struct qpnp_tri_led_chip *chip = dev_get_drvdata(&pdev->dev); mutex_destroy(&chip->bus_lock); for (i = 0; i < chip->num_leds; i++) mutex_destroy(&chip->leds[i].lock); dev_set_drvdata(chip->dev, NULL); return 0; } static const struct of_device_id qpnp_tri_led_of_match[] = { { .compatible = "qcom,tri-led",}, { }, }; static struct platform_driver qpnp_tri_led_driver = { .driver = { .name = "qcom,tri-led", .of_match_table = qpnp_tri_led_of_match, }, .probe = qpnp_tri_led_probe, .remove = qpnp_tri_led_remove, }; module_platform_driver(qpnp_tri_led_driver); MODULE_DESCRIPTION("QTI TRI_LED driver"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("leds:qpnp-tri-led");