Loading Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt 0 → 100644 +90 −0 Original line number Diff line number Diff line Bindings for Qualcomm Technologies, Inc. WLED driver WLED (White Light Emitting Diode) driver is used for controlling display backlight that is part of PMIC on Qualcomm Technologies, Inc. reference platforms. The PMIC is connected to the host processor via SPMI bus. - compatible Usage: required Value type: <string> Definition: should be "qcom,pmi8998-spmi-wled". - reg Usage: required Value type: <prop-encoded-array> Definition: Base address and size of the WLED modules. - reg-names Usage: required Value type: <string> Definition: Names associated with base addresses. should be "wled-ctrl-base", "wled-sink-base". - label Usage: required Value type: <string> Definition: The name of the backlight device. - default-brightness Usage: optional Value type: <u32> Definition: brightness value on boot, value from: 0-4095 Default: 2048 - qcom,fs-current-limit Usage: optional Value type: <u32> Definition: per-string full scale current limit in uA. value from 0 to 30000 with 5000 uA resolution. Default: 25000 uA - qcom,current-boost-limit Usage: optional Value type: <u32> Definition: ILIM threshold in mA. values are 105, 280, 450, 620, 970, 1150, 1300, 1500. Default: 970 mA - qcom,switching-freq Usage: optional Value type: <u32> Definition: Switching frequency in KHz. values are 600, 640, 685, 738, 800, 872, 960, 1066, 1200, 1371, 1600, 1920, 2400, 3200, 4800, 9600. Default: 800 KHz - qcom,ovp Usage: optional Value type: <u32> Definition: Over-voltage protection limit in mV. values are 31100, 29600, 19600, 18100. Default: 29600 mV - qcom,string-cfg Usage: optional Value type: <u32> Definition: Bit mask of the WLED strings. Bit 0 to 3 indicates strings 0 to 3 respectively. WLED module has four strings of leds numbered from 0 to 3. Each string of leds are operated individually. Specify the strings using the bit mask. Any combination of led strings can be used. Default value is 15 (b1111). - qcom,en-cabc Usage: optional Value type: <bool> Definition: Specify if cabc (content adaptive backlight control) is needed. Example: qcom-wled@d800 { compatible = "qcom,pmi8998-spmi-wled"; reg = <0xd800 0xd900>; reg-names = "wled-ctrl-base", "wled-sink-base"; label = "backlight"; qcom,fs-current-limit = <25000>; qcom,current-boost-limit = <970>; qcom,switching-freq = <800>; qcom,ovp = <29600>; qcom,string-cfg = <15>; }; drivers/video/backlight/Kconfig +9 −0 Original line number Diff line number Diff line Loading @@ -306,6 +306,15 @@ config BACKLIGHT_PM8941_WLED If you have the Qualcomm PM8941, say Y to enable a driver for the WLED block. config BACKLIGHT_QCOM_SPMI_WLED tristate "Qualcomm Technologies, Inc. WLED Driver" select REGMAP help If you have the Qualcomm Technologies, Inc. WLED used for backlight control, say Y to enable a driver for the WLED block. This driver provides the interface to the display driver to adjust the brightness of the display backlight. This supports PMI8998 currently. config BACKLIGHT_SAHARA tristate "Tabletkiosk Sahara Touch-iT Backlight Driver" depends on X86 Loading drivers/video/backlight/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ obj-$(CONFIG_BACKLIGHT_PANDORA) += pandora_bl.o obj-$(CONFIG_BACKLIGHT_PCF50633) += pcf50633-backlight.o obj-$(CONFIG_BACKLIGHT_PM8941_WLED) += pm8941-wled.o obj-$(CONFIG_BACKLIGHT_PWM) += pwm_bl.o obj-$(CONFIG_BACKLIGHT_QCOM_SPMI_WLED) += qcom-spmi-wled.o obj-$(CONFIG_BACKLIGHT_SAHARA) += kb3886_bl.o obj-$(CONFIG_BACKLIGHT_SKY81452) += sky81452-backlight.o obj-$(CONFIG_BACKLIGHT_TOSA) += tosa_bl.o Loading drivers/video/backlight/qcom-spmi-wled.c 0 → 100644 +507 −0 Original line number Diff line number Diff line /* Copyright (c) 2015, Sony Mobile Communications, AB. * * 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. */ #define pr_fmt(fmt) "WLED: %s: " fmt, __func__ #include <linux/kernel.h> #include <linux/backlight.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/of_address.h> #include <linux/regmap.h> /* General definitions */ #define WLED_DEFAULT_BRIGHTNESS 2048 #define WLED_MAX_BRIGHTNESS 4095 /* WLED control registers */ #define WLED_CTRL_MOD_ENABLE 0x46 #define WLED_CTRL_MOD_EN_MASK BIT(7) #define WLED_CTRL_MODULE_EN_SHIFT 7 #define WLED_CTRL_SWITCH_FREQ 0x4c #define WLED_CTRL_SWITCH_FREQ_MASK GENMASK(3, 0) #define WLED_CTRL_OVP 0x4d #define WLED_CTRL_OVP_MASK GENMASK(1, 0) #define WLED_CTRL_ILIM 0x4e #define WLED_CTRL_ILIM_MASK GENMASK(2, 0) /* WLED sink registers */ #define WLED_SINK_CURR_SINK_EN 0x46 #define WLED_SINK_CURR_SINK_MASK GENMASK(7, 4) #define WLED_SINK_CURR_SINK_SHFT 0x04 #define WLED_SINK_SYNC 0x47 #define WLED_SINK_SYNC_MASK GENMASK(3, 0) #define WLED_SINK_SYNC_LED1 BIT(0) #define WLED_SINK_SYNC_LED2 BIT(1) #define WLED_SINK_SYNC_LED3 BIT(2) #define WLED_SINK_SYNC_LED4 BIT(3) #define WLED_SINK_SYNC_CLEAR 0x00 #define WLED_SINK_MOD_EN_REG(n) (0x50 + (n * 0x10)) #define WLED_SINK_REG_STR_MOD_MASK BIT(7) #define WLED_SINK_REG_STR_MOD_EN BIT(7) #define WLED_SINK_SYNC_DLY_REG(n) (0x51 + (n * 0x10)) #define WLED_SINK_FS_CURR_REG(n) (0x52 + (n * 0x10)) #define WLED_SINK_FS_MASK GENMASK(3, 0) #define WLED_SINK_CABC_REG(n) (0x56 + (n * 0x10)) #define WLED_SINK_CABC_MASK BIT(7) #define WLED_SINK_CABC_EN BIT(7) #define WLED_SINK_BRIGHT_LSB_REG(n) (0x57 + (n * 0x10)) #define WLED_SINK_BRIGHT_MSB_REG(n) (0x58 + (n * 0x10)) struct wled_config { u32 i_boost_limit; u32 ovp; u32 switch_freq; u32 fs_current; u32 string_cfg; bool en_cabc; }; struct wled { const char *name; struct platform_device *pdev; struct regmap *regmap; u16 sink_addr; u16 ctrl_addr; u32 brightness; bool prev_state; struct wled_config cfg; }; static int wled_module_enable(struct wled *wled, int val) { int rc; rc = regmap_update_bits(wled->regmap, wled->ctrl_addr + WLED_CTRL_MOD_ENABLE, WLED_CTRL_MOD_EN_MASK, val << WLED_CTRL_MODULE_EN_SHIFT); return rc; } static int wled_get_brightness(struct backlight_device *bl) { struct wled *wled = bl_get_data(bl); return wled->brightness; } static int wled_sync_toggle(struct wled *wled) { int rc; rc = regmap_update_bits(wled->regmap, wled->sink_addr + WLED_SINK_SYNC, WLED_SINK_SYNC_MASK, WLED_SINK_SYNC_MASK); if (rc < 0) return rc; rc = regmap_update_bits(wled->regmap, wled->sink_addr + WLED_SINK_SYNC, WLED_SINK_SYNC_MASK, WLED_SINK_SYNC_CLEAR); return rc; } static int wled_set_brightness(struct wled *wled, u16 brightness) { int rc, i; u16 low_limit = WLED_MAX_BRIGHTNESS * 4 / 1000; u8 string_cfg = wled->cfg.string_cfg; u8 v[2]; /* WLED's lower limit of operation is 0.4% */ if (brightness > 0 && brightness < low_limit) brightness = low_limit; v[0] = brightness & 0xff; v[1] = (brightness >> 8) & 0xf; for (i = 0; (string_cfg >> i) != 0; i++) { if (string_cfg & BIT(i)) { rc = regmap_bulk_write(wled->regmap, wled->sink_addr + WLED_SINK_BRIGHT_LSB_REG(i), v, 2); if (rc < 0) return rc; } } return 0; } static int wled_update_status(struct backlight_device *bl) { struct wled *wled = bl_get_data(bl); u16 brightness = bl->props.brightness; int rc; if (bl->props.power != FB_BLANK_UNBLANK || bl->props.fb_blank != FB_BLANK_UNBLANK || bl->props.state & BL_CORE_FBBLANK) brightness = 0; if (brightness) { rc = wled_set_brightness(wled, brightness); if (rc < 0) { pr_err("wled failed to set brightness rc:%d\n", rc); return rc; } if (!!brightness != wled->prev_state) { rc = wled_module_enable(wled, !!brightness); if (rc < 0) { pr_err("wled enable failed rc:%d\n", rc); return rc; } } } else { rc = wled_module_enable(wled, brightness); if (rc < 0) { pr_err("wled disable failed rc:%d\n", rc); return rc; } } wled->prev_state = !!brightness; rc = wled_sync_toggle(wled); if (rc < 0) { pr_err("wled sync failed rc:%d\n", rc); return rc; } wled->brightness = brightness; return rc; } static int wled_setup(struct wled *wled) { int rc, temp, i; u8 sink_en = 0; u8 string_cfg = wled->cfg.string_cfg; rc = regmap_update_bits(wled->regmap, wled->ctrl_addr + WLED_CTRL_OVP, WLED_CTRL_OVP_MASK, wled->cfg.ovp); if (rc < 0) return rc; rc = regmap_update_bits(wled->regmap, wled->ctrl_addr + WLED_CTRL_ILIM, WLED_CTRL_ILIM_MASK, wled->cfg.i_boost_limit); if (rc < 0) return rc; rc = regmap_update_bits(wled->regmap, wled->ctrl_addr + WLED_CTRL_SWITCH_FREQ, WLED_CTRL_SWITCH_FREQ_MASK, wled->cfg.switch_freq); if (rc < 0) return rc; for (i = 0; (string_cfg >> i) != 0; i++) { if (string_cfg & BIT(i)) { u16 addr = wled->sink_addr + WLED_SINK_MOD_EN_REG(i); rc = regmap_update_bits(wled->regmap, addr, WLED_SINK_REG_STR_MOD_MASK, WLED_SINK_REG_STR_MOD_EN); if (rc < 0) return rc; addr = wled->sink_addr + WLED_SINK_FS_CURR_REG(i); rc = regmap_update_bits(wled->regmap, addr, WLED_SINK_FS_MASK, wled->cfg.fs_current); if (rc < 0) return rc; addr = wled->sink_addr + WLED_SINK_CABC_REG(i); rc = regmap_update_bits(wled->regmap, addr, WLED_SINK_CABC_MASK, wled->cfg.en_cabc ? WLED_SINK_CABC_EN : 0); if (rc) return rc; temp = i + WLED_SINK_CURR_SINK_SHFT; sink_en |= 1 << temp; } } rc = regmap_update_bits(wled->regmap, wled->sink_addr + WLED_SINK_CURR_SINK_EN, WLED_SINK_CURR_SINK_MASK, sink_en); if (rc < 0) return rc; rc = wled_sync_toggle(wled); if (rc < 0) { pr_err("Failed to toggle sync reg rc:%d\n", rc); return rc; } return 0; } static const struct wled_config wled_config_defaults = { .i_boost_limit = 4, .fs_current = 10, .ovp = 1, .switch_freq = 11, .string_cfg = 0xf, .en_cabc = 0, }; struct wled_var_cfg { const u32 *values; u32 (*fn)(u32); int size; }; static const u32 wled_i_boost_limit_values[] = { 105, 280, 450, 620, 970, 1150, 1300, 1500, }; static const struct wled_var_cfg wled_i_boost_limit_cfg = { .values = wled_i_boost_limit_values, .size = ARRAY_SIZE(wled_i_boost_limit_values), }; static const u32 wled_fs_current_values[] = { 0, 2500, 5000, 7500, 10000, 12500, 15000, 17500, 20000, 22500, 25000, 27500, 30000, }; static const struct wled_var_cfg wled_fs_current_cfg = { .values = wled_fs_current_values, .size = ARRAY_SIZE(wled_fs_current_values), }; static const u32 wled_ovp_values[] = { 31100, 29600, 19600, 18100, }; static const struct wled_var_cfg wled_ovp_cfg = { .values = wled_ovp_values, .size = ARRAY_SIZE(wled_ovp_values), }; static u32 wled_switch_freq_values_fn(u32 idx) { return 9600 / (1 + idx); } static const struct wled_var_cfg wled_switch_freq_cfg = { .fn = wled_switch_freq_values_fn, .size = 16, }; static const struct wled_var_cfg wled_string_cfg = { .size = 16, }; static u32 wled_values(const struct wled_var_cfg *cfg, u32 idx) { if (idx >= cfg->size) return UINT_MAX; if (cfg->fn) return cfg->fn(idx); if (cfg->values) return cfg->values[idx]; return idx; } static int wled_configure(struct wled *wled, struct device *dev) { struct wled_config *cfg = &wled->cfg; const __be32 *prop_addr; u32 val, c; int rc, i, j; const struct { const char *name; u32 *val_ptr; const struct wled_var_cfg *cfg; } u32_opts[] = { { "qcom,current-boost-limit", &cfg->i_boost_limit, .cfg = &wled_i_boost_limit_cfg, }, { "qcom,fs-current-limit", &cfg->fs_current, .cfg = &wled_fs_current_cfg, }, { "qcom,ovp", &cfg->ovp, .cfg = &wled_ovp_cfg, }, { "qcom,switching-freq", &cfg->switch_freq, .cfg = &wled_switch_freq_cfg, }, { "qcom,string-cfg", &cfg->string_cfg, .cfg = &wled_string_cfg, }, }; const struct { const char *name; bool *val_ptr; } bool_opts[] = { { "qcom,en-cabc", &cfg->en_cabc, }, }; prop_addr = of_get_address(dev->of_node, 0, NULL, NULL); if (!prop_addr) { pr_err("invalid IO resources\n"); return -EINVAL; } wled->ctrl_addr = be32_to_cpu(*prop_addr); prop_addr = of_get_address(dev->of_node, 1, NULL, NULL); if (!prop_addr) { pr_err("invalid IO resources\n"); return -EINVAL; } wled->sink_addr = be32_to_cpu(*prop_addr); rc = of_property_read_string(dev->of_node, "label", &wled->name); if (rc < 0) wled->name = dev->of_node->name; *cfg = wled_config_defaults; for (i = 0; i < ARRAY_SIZE(u32_opts); ++i) { rc = of_property_read_u32(dev->of_node, u32_opts[i].name, &val); if (rc == -EINVAL) { continue; } else if (rc < 0) { pr_err("error reading '%s'\n", u32_opts[i].name); return rc; } c = UINT_MAX; for (j = 0; c != val; j++) { c = wled_values(u32_opts[i].cfg, j); if (c == UINT_MAX) { pr_err("invalid value for '%s'\n", u32_opts[i].name); return -EINVAL; } if (c == val) break; } pr_debug("'%s' = %u\n", u32_opts[i].name, c); *u32_opts[i].val_ptr = j; } for (i = 0; i < ARRAY_SIZE(bool_opts); ++i) { if (of_property_read_bool(dev->of_node, bool_opts[i].name)) *bool_opts[i].val_ptr = true; } return 0; } static const struct backlight_ops wled_ops = { .update_status = wled_update_status, .get_brightness = wled_get_brightness, }; static int wled_probe(struct platform_device *pdev) { struct backlight_properties props; struct backlight_device *bl; struct wled *wled; struct regmap *regmap; u32 val; int rc; regmap = dev_get_regmap(pdev->dev.parent, NULL); if (!regmap) { pr_err("Unable to get regmap\n"); return -EINVAL; } wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL); if (!wled) return -ENOMEM; wled->regmap = regmap; wled->pdev = pdev; rc = wled_configure(wled, &pdev->dev); if (rc < 0) { pr_err("wled configure failed rc:%d\n", rc); return rc; } rc = wled_setup(wled); if (rc < 0) { pr_err("wled setup failed rc:%d\n", rc); return rc; } val = WLED_DEFAULT_BRIGHTNESS; of_property_read_u32(pdev->dev.of_node, "default-brightness", &val); wled->brightness = val; platform_set_drvdata(pdev, wled); memset(&props, 0, sizeof(struct backlight_properties)); props.type = BACKLIGHT_RAW; props.brightness = val; props.max_brightness = WLED_MAX_BRIGHTNESS; bl = devm_backlight_device_register(&pdev->dev, pdev->name, &pdev->dev, wled, &wled_ops, &props); return PTR_ERR_OR_ZERO(bl); } static const struct of_device_id wled_match_table[] = { { .compatible = "qcom,pmi8998-spmi-wled",}, { }, }; static struct platform_driver wled_driver = { .probe = wled_probe, .driver = { .name = "qcom-spmi-wled", .of_match_table = wled_match_table, }, }; module_platform_driver(wled_driver); MODULE_DESCRIPTION("Qualcomm Technologies, Inc. SPMI PMIC WLED driver"); MODULE_LICENSE("GPL v2"); Loading
Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt 0 → 100644 +90 −0 Original line number Diff line number Diff line Bindings for Qualcomm Technologies, Inc. WLED driver WLED (White Light Emitting Diode) driver is used for controlling display backlight that is part of PMIC on Qualcomm Technologies, Inc. reference platforms. The PMIC is connected to the host processor via SPMI bus. - compatible Usage: required Value type: <string> Definition: should be "qcom,pmi8998-spmi-wled". - reg Usage: required Value type: <prop-encoded-array> Definition: Base address and size of the WLED modules. - reg-names Usage: required Value type: <string> Definition: Names associated with base addresses. should be "wled-ctrl-base", "wled-sink-base". - label Usage: required Value type: <string> Definition: The name of the backlight device. - default-brightness Usage: optional Value type: <u32> Definition: brightness value on boot, value from: 0-4095 Default: 2048 - qcom,fs-current-limit Usage: optional Value type: <u32> Definition: per-string full scale current limit in uA. value from 0 to 30000 with 5000 uA resolution. Default: 25000 uA - qcom,current-boost-limit Usage: optional Value type: <u32> Definition: ILIM threshold in mA. values are 105, 280, 450, 620, 970, 1150, 1300, 1500. Default: 970 mA - qcom,switching-freq Usage: optional Value type: <u32> Definition: Switching frequency in KHz. values are 600, 640, 685, 738, 800, 872, 960, 1066, 1200, 1371, 1600, 1920, 2400, 3200, 4800, 9600. Default: 800 KHz - qcom,ovp Usage: optional Value type: <u32> Definition: Over-voltage protection limit in mV. values are 31100, 29600, 19600, 18100. Default: 29600 mV - qcom,string-cfg Usage: optional Value type: <u32> Definition: Bit mask of the WLED strings. Bit 0 to 3 indicates strings 0 to 3 respectively. WLED module has four strings of leds numbered from 0 to 3. Each string of leds are operated individually. Specify the strings using the bit mask. Any combination of led strings can be used. Default value is 15 (b1111). - qcom,en-cabc Usage: optional Value type: <bool> Definition: Specify if cabc (content adaptive backlight control) is needed. Example: qcom-wled@d800 { compatible = "qcom,pmi8998-spmi-wled"; reg = <0xd800 0xd900>; reg-names = "wled-ctrl-base", "wled-sink-base"; label = "backlight"; qcom,fs-current-limit = <25000>; qcom,current-boost-limit = <970>; qcom,switching-freq = <800>; qcom,ovp = <29600>; qcom,string-cfg = <15>; };
drivers/video/backlight/Kconfig +9 −0 Original line number Diff line number Diff line Loading @@ -306,6 +306,15 @@ config BACKLIGHT_PM8941_WLED If you have the Qualcomm PM8941, say Y to enable a driver for the WLED block. config BACKLIGHT_QCOM_SPMI_WLED tristate "Qualcomm Technologies, Inc. WLED Driver" select REGMAP help If you have the Qualcomm Technologies, Inc. WLED used for backlight control, say Y to enable a driver for the WLED block. This driver provides the interface to the display driver to adjust the brightness of the display backlight. This supports PMI8998 currently. config BACKLIGHT_SAHARA tristate "Tabletkiosk Sahara Touch-iT Backlight Driver" depends on X86 Loading
drivers/video/backlight/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ obj-$(CONFIG_BACKLIGHT_PANDORA) += pandora_bl.o obj-$(CONFIG_BACKLIGHT_PCF50633) += pcf50633-backlight.o obj-$(CONFIG_BACKLIGHT_PM8941_WLED) += pm8941-wled.o obj-$(CONFIG_BACKLIGHT_PWM) += pwm_bl.o obj-$(CONFIG_BACKLIGHT_QCOM_SPMI_WLED) += qcom-spmi-wled.o obj-$(CONFIG_BACKLIGHT_SAHARA) += kb3886_bl.o obj-$(CONFIG_BACKLIGHT_SKY81452) += sky81452-backlight.o obj-$(CONFIG_BACKLIGHT_TOSA) += tosa_bl.o Loading
drivers/video/backlight/qcom-spmi-wled.c 0 → 100644 +507 −0 Original line number Diff line number Diff line /* Copyright (c) 2015, Sony Mobile Communications, AB. * * 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. */ #define pr_fmt(fmt) "WLED: %s: " fmt, __func__ #include <linux/kernel.h> #include <linux/backlight.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/of_address.h> #include <linux/regmap.h> /* General definitions */ #define WLED_DEFAULT_BRIGHTNESS 2048 #define WLED_MAX_BRIGHTNESS 4095 /* WLED control registers */ #define WLED_CTRL_MOD_ENABLE 0x46 #define WLED_CTRL_MOD_EN_MASK BIT(7) #define WLED_CTRL_MODULE_EN_SHIFT 7 #define WLED_CTRL_SWITCH_FREQ 0x4c #define WLED_CTRL_SWITCH_FREQ_MASK GENMASK(3, 0) #define WLED_CTRL_OVP 0x4d #define WLED_CTRL_OVP_MASK GENMASK(1, 0) #define WLED_CTRL_ILIM 0x4e #define WLED_CTRL_ILIM_MASK GENMASK(2, 0) /* WLED sink registers */ #define WLED_SINK_CURR_SINK_EN 0x46 #define WLED_SINK_CURR_SINK_MASK GENMASK(7, 4) #define WLED_SINK_CURR_SINK_SHFT 0x04 #define WLED_SINK_SYNC 0x47 #define WLED_SINK_SYNC_MASK GENMASK(3, 0) #define WLED_SINK_SYNC_LED1 BIT(0) #define WLED_SINK_SYNC_LED2 BIT(1) #define WLED_SINK_SYNC_LED3 BIT(2) #define WLED_SINK_SYNC_LED4 BIT(3) #define WLED_SINK_SYNC_CLEAR 0x00 #define WLED_SINK_MOD_EN_REG(n) (0x50 + (n * 0x10)) #define WLED_SINK_REG_STR_MOD_MASK BIT(7) #define WLED_SINK_REG_STR_MOD_EN BIT(7) #define WLED_SINK_SYNC_DLY_REG(n) (0x51 + (n * 0x10)) #define WLED_SINK_FS_CURR_REG(n) (0x52 + (n * 0x10)) #define WLED_SINK_FS_MASK GENMASK(3, 0) #define WLED_SINK_CABC_REG(n) (0x56 + (n * 0x10)) #define WLED_SINK_CABC_MASK BIT(7) #define WLED_SINK_CABC_EN BIT(7) #define WLED_SINK_BRIGHT_LSB_REG(n) (0x57 + (n * 0x10)) #define WLED_SINK_BRIGHT_MSB_REG(n) (0x58 + (n * 0x10)) struct wled_config { u32 i_boost_limit; u32 ovp; u32 switch_freq; u32 fs_current; u32 string_cfg; bool en_cabc; }; struct wled { const char *name; struct platform_device *pdev; struct regmap *regmap; u16 sink_addr; u16 ctrl_addr; u32 brightness; bool prev_state; struct wled_config cfg; }; static int wled_module_enable(struct wled *wled, int val) { int rc; rc = regmap_update_bits(wled->regmap, wled->ctrl_addr + WLED_CTRL_MOD_ENABLE, WLED_CTRL_MOD_EN_MASK, val << WLED_CTRL_MODULE_EN_SHIFT); return rc; } static int wled_get_brightness(struct backlight_device *bl) { struct wled *wled = bl_get_data(bl); return wled->brightness; } static int wled_sync_toggle(struct wled *wled) { int rc; rc = regmap_update_bits(wled->regmap, wled->sink_addr + WLED_SINK_SYNC, WLED_SINK_SYNC_MASK, WLED_SINK_SYNC_MASK); if (rc < 0) return rc; rc = regmap_update_bits(wled->regmap, wled->sink_addr + WLED_SINK_SYNC, WLED_SINK_SYNC_MASK, WLED_SINK_SYNC_CLEAR); return rc; } static int wled_set_brightness(struct wled *wled, u16 brightness) { int rc, i; u16 low_limit = WLED_MAX_BRIGHTNESS * 4 / 1000; u8 string_cfg = wled->cfg.string_cfg; u8 v[2]; /* WLED's lower limit of operation is 0.4% */ if (brightness > 0 && brightness < low_limit) brightness = low_limit; v[0] = brightness & 0xff; v[1] = (brightness >> 8) & 0xf; for (i = 0; (string_cfg >> i) != 0; i++) { if (string_cfg & BIT(i)) { rc = regmap_bulk_write(wled->regmap, wled->sink_addr + WLED_SINK_BRIGHT_LSB_REG(i), v, 2); if (rc < 0) return rc; } } return 0; } static int wled_update_status(struct backlight_device *bl) { struct wled *wled = bl_get_data(bl); u16 brightness = bl->props.brightness; int rc; if (bl->props.power != FB_BLANK_UNBLANK || bl->props.fb_blank != FB_BLANK_UNBLANK || bl->props.state & BL_CORE_FBBLANK) brightness = 0; if (brightness) { rc = wled_set_brightness(wled, brightness); if (rc < 0) { pr_err("wled failed to set brightness rc:%d\n", rc); return rc; } if (!!brightness != wled->prev_state) { rc = wled_module_enable(wled, !!brightness); if (rc < 0) { pr_err("wled enable failed rc:%d\n", rc); return rc; } } } else { rc = wled_module_enable(wled, brightness); if (rc < 0) { pr_err("wled disable failed rc:%d\n", rc); return rc; } } wled->prev_state = !!brightness; rc = wled_sync_toggle(wled); if (rc < 0) { pr_err("wled sync failed rc:%d\n", rc); return rc; } wled->brightness = brightness; return rc; } static int wled_setup(struct wled *wled) { int rc, temp, i; u8 sink_en = 0; u8 string_cfg = wled->cfg.string_cfg; rc = regmap_update_bits(wled->regmap, wled->ctrl_addr + WLED_CTRL_OVP, WLED_CTRL_OVP_MASK, wled->cfg.ovp); if (rc < 0) return rc; rc = regmap_update_bits(wled->regmap, wled->ctrl_addr + WLED_CTRL_ILIM, WLED_CTRL_ILIM_MASK, wled->cfg.i_boost_limit); if (rc < 0) return rc; rc = regmap_update_bits(wled->regmap, wled->ctrl_addr + WLED_CTRL_SWITCH_FREQ, WLED_CTRL_SWITCH_FREQ_MASK, wled->cfg.switch_freq); if (rc < 0) return rc; for (i = 0; (string_cfg >> i) != 0; i++) { if (string_cfg & BIT(i)) { u16 addr = wled->sink_addr + WLED_SINK_MOD_EN_REG(i); rc = regmap_update_bits(wled->regmap, addr, WLED_SINK_REG_STR_MOD_MASK, WLED_SINK_REG_STR_MOD_EN); if (rc < 0) return rc; addr = wled->sink_addr + WLED_SINK_FS_CURR_REG(i); rc = regmap_update_bits(wled->regmap, addr, WLED_SINK_FS_MASK, wled->cfg.fs_current); if (rc < 0) return rc; addr = wled->sink_addr + WLED_SINK_CABC_REG(i); rc = regmap_update_bits(wled->regmap, addr, WLED_SINK_CABC_MASK, wled->cfg.en_cabc ? WLED_SINK_CABC_EN : 0); if (rc) return rc; temp = i + WLED_SINK_CURR_SINK_SHFT; sink_en |= 1 << temp; } } rc = regmap_update_bits(wled->regmap, wled->sink_addr + WLED_SINK_CURR_SINK_EN, WLED_SINK_CURR_SINK_MASK, sink_en); if (rc < 0) return rc; rc = wled_sync_toggle(wled); if (rc < 0) { pr_err("Failed to toggle sync reg rc:%d\n", rc); return rc; } return 0; } static const struct wled_config wled_config_defaults = { .i_boost_limit = 4, .fs_current = 10, .ovp = 1, .switch_freq = 11, .string_cfg = 0xf, .en_cabc = 0, }; struct wled_var_cfg { const u32 *values; u32 (*fn)(u32); int size; }; static const u32 wled_i_boost_limit_values[] = { 105, 280, 450, 620, 970, 1150, 1300, 1500, }; static const struct wled_var_cfg wled_i_boost_limit_cfg = { .values = wled_i_boost_limit_values, .size = ARRAY_SIZE(wled_i_boost_limit_values), }; static const u32 wled_fs_current_values[] = { 0, 2500, 5000, 7500, 10000, 12500, 15000, 17500, 20000, 22500, 25000, 27500, 30000, }; static const struct wled_var_cfg wled_fs_current_cfg = { .values = wled_fs_current_values, .size = ARRAY_SIZE(wled_fs_current_values), }; static const u32 wled_ovp_values[] = { 31100, 29600, 19600, 18100, }; static const struct wled_var_cfg wled_ovp_cfg = { .values = wled_ovp_values, .size = ARRAY_SIZE(wled_ovp_values), }; static u32 wled_switch_freq_values_fn(u32 idx) { return 9600 / (1 + idx); } static const struct wled_var_cfg wled_switch_freq_cfg = { .fn = wled_switch_freq_values_fn, .size = 16, }; static const struct wled_var_cfg wled_string_cfg = { .size = 16, }; static u32 wled_values(const struct wled_var_cfg *cfg, u32 idx) { if (idx >= cfg->size) return UINT_MAX; if (cfg->fn) return cfg->fn(idx); if (cfg->values) return cfg->values[idx]; return idx; } static int wled_configure(struct wled *wled, struct device *dev) { struct wled_config *cfg = &wled->cfg; const __be32 *prop_addr; u32 val, c; int rc, i, j; const struct { const char *name; u32 *val_ptr; const struct wled_var_cfg *cfg; } u32_opts[] = { { "qcom,current-boost-limit", &cfg->i_boost_limit, .cfg = &wled_i_boost_limit_cfg, }, { "qcom,fs-current-limit", &cfg->fs_current, .cfg = &wled_fs_current_cfg, }, { "qcom,ovp", &cfg->ovp, .cfg = &wled_ovp_cfg, }, { "qcom,switching-freq", &cfg->switch_freq, .cfg = &wled_switch_freq_cfg, }, { "qcom,string-cfg", &cfg->string_cfg, .cfg = &wled_string_cfg, }, }; const struct { const char *name; bool *val_ptr; } bool_opts[] = { { "qcom,en-cabc", &cfg->en_cabc, }, }; prop_addr = of_get_address(dev->of_node, 0, NULL, NULL); if (!prop_addr) { pr_err("invalid IO resources\n"); return -EINVAL; } wled->ctrl_addr = be32_to_cpu(*prop_addr); prop_addr = of_get_address(dev->of_node, 1, NULL, NULL); if (!prop_addr) { pr_err("invalid IO resources\n"); return -EINVAL; } wled->sink_addr = be32_to_cpu(*prop_addr); rc = of_property_read_string(dev->of_node, "label", &wled->name); if (rc < 0) wled->name = dev->of_node->name; *cfg = wled_config_defaults; for (i = 0; i < ARRAY_SIZE(u32_opts); ++i) { rc = of_property_read_u32(dev->of_node, u32_opts[i].name, &val); if (rc == -EINVAL) { continue; } else if (rc < 0) { pr_err("error reading '%s'\n", u32_opts[i].name); return rc; } c = UINT_MAX; for (j = 0; c != val; j++) { c = wled_values(u32_opts[i].cfg, j); if (c == UINT_MAX) { pr_err("invalid value for '%s'\n", u32_opts[i].name); return -EINVAL; } if (c == val) break; } pr_debug("'%s' = %u\n", u32_opts[i].name, c); *u32_opts[i].val_ptr = j; } for (i = 0; i < ARRAY_SIZE(bool_opts); ++i) { if (of_property_read_bool(dev->of_node, bool_opts[i].name)) *bool_opts[i].val_ptr = true; } return 0; } static const struct backlight_ops wled_ops = { .update_status = wled_update_status, .get_brightness = wled_get_brightness, }; static int wled_probe(struct platform_device *pdev) { struct backlight_properties props; struct backlight_device *bl; struct wled *wled; struct regmap *regmap; u32 val; int rc; regmap = dev_get_regmap(pdev->dev.parent, NULL); if (!regmap) { pr_err("Unable to get regmap\n"); return -EINVAL; } wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL); if (!wled) return -ENOMEM; wled->regmap = regmap; wled->pdev = pdev; rc = wled_configure(wled, &pdev->dev); if (rc < 0) { pr_err("wled configure failed rc:%d\n", rc); return rc; } rc = wled_setup(wled); if (rc < 0) { pr_err("wled setup failed rc:%d\n", rc); return rc; } val = WLED_DEFAULT_BRIGHTNESS; of_property_read_u32(pdev->dev.of_node, "default-brightness", &val); wled->brightness = val; platform_set_drvdata(pdev, wled); memset(&props, 0, sizeof(struct backlight_properties)); props.type = BACKLIGHT_RAW; props.brightness = val; props.max_brightness = WLED_MAX_BRIGHTNESS; bl = devm_backlight_device_register(&pdev->dev, pdev->name, &pdev->dev, wled, &wled_ops, &props); return PTR_ERR_OR_ZERO(bl); } static const struct of_device_id wled_match_table[] = { { .compatible = "qcom,pmi8998-spmi-wled",}, { }, }; static struct platform_driver wled_driver = { .probe = wled_probe, .driver = { .name = "qcom-spmi-wled", .of_match_table = wled_match_table, }, }; module_platform_driver(wled_driver); MODULE_DESCRIPTION("Qualcomm Technologies, Inc. SPMI PMIC WLED driver"); MODULE_LICENSE("GPL v2");