Re: [PATCH v3 2/2] leds: add Qualcomm PM8941 WLED driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




On Tue, Feb 24, 2015 at 7:49 PM, Bjorn Andersson
<bjorn.andersson@xxxxxxxxxxxxxx> wrote:
> From: Courtney Cavin <courtney.cavin@xxxxxxxxxxxxxx>
>
> This adds support for the WLED ('White' LED) block on Qualcomm's
> PM8941 PMICs.
>
> Signed-off-by: Courtney Cavin <courtney.cavin@xxxxxxxxxxxxxx>
> Signed-off-by: Bjorn Andersson <bjorn.andersson@xxxxxxxxxxxxxx>
> ---
>
> The details surrounding the OVP spike issue that Stephen brought up during v2
> review is still unkown, the patches provided on codeaurora msm-3.10 can with
> ease be implemented as an incremental patch on top of this.
> As the severity doesn't seem to high (the fixes are not done on all active msm
> branches), I'm sending out v3 instead of waiting for a conclusion.
>
> Changes since v2:
> - Fix of parser return value on error
> - Dropped THIS_MODULE
>
> Changes since v1:
> - Replace enum blob with defines
> - Merged wled_context and wled structs
> - Some style updates
>
>  drivers/leds/Kconfig            |   8 +
>  drivers/leds/Makefile           |   1 +
>  drivers/leds/leds-pm8941-wled.c | 446 ++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 455 insertions(+)
>  create mode 100644 drivers/leds/leds-pm8941-wled.c
>
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index a6c3d2f..901b21a 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -516,6 +516,14 @@ config LEDS_VERSATILE
>           This option enabled support for the LEDs on the ARM Versatile
>           and RealView boards. Say Y to enabled these.
>
> +config LEDS_PM8941_WLED
> +       tristate "LED support for the Qualcomm PM8941 WLED block"
> +       depends on LEDS_CLASS
> +       select REGMAP
> +       help
> +         This option enables support for the 'White' LED block
> +         on Qualcomm PM8941 PMICs.
> +
>  comment "LED Triggers"
>  source "drivers/leds/trigger/Kconfig"
>
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index 1c65a19..4325e49 100644
> --- a/drivers/leds/Makefile
> +++ b/drivers/leds/Makefile
> @@ -57,6 +57,7 @@ obj-$(CONFIG_LEDS_BLINKM)             += leds-blinkm.o
>  obj-$(CONFIG_LEDS_SYSCON)              += leds-syscon.o
>  obj-$(CONFIG_LEDS_VERSATILE)           += leds-versatile.o
>  obj-$(CONFIG_LEDS_MENF21BMC)           += leds-menf21bmc.o
> +obj-$(CONFIG_LEDS_PM8941_WLED)         += leds-pm8941-wled.o
>
>  # LED SPI Drivers
>  obj-$(CONFIG_LEDS_DAC124S085)          += leds-dac124s085.o
> diff --git a/drivers/leds/leds-pm8941-wled.c b/drivers/leds/leds-pm8941-wled.c
> new file mode 100644
> index 0000000..9bf7358
> --- /dev/null
> +++ b/drivers/leds/leds-pm8941-wled.c
> @@ -0,0 +1,446 @@
> +/* Copyright (c) 2015, Sony Mobile Communications, AB.
> + *
> + * 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/kernel.h>
> +#include <linux/leds.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/regmap.h>
> +
> +#define PM8941_WLED_REG_VAL_BASE               0x40
> +#define  PM8941_WLED_REG_VAL_MAX               0xFFF
> +
> +#define PM8941_WLED_REG_MOD_EN                 0x46
> +#define  PM8941_WLED_REG_MOD_EN_BIT            BIT(7)
> +#define  PM8941_WLED_REG_MOD_EN_MASK           BIT(7)
> +
> +#define PM8941_WLED_REG_SYNC                   0x47
> +#define  PM8941_WLED_REG_SYNC_MASK             0x07
> +#define  PM8941_WLED_REG_SYNC_LED1             BIT(0)
> +#define  PM8941_WLED_REG_SYNC_LED2             BIT(1)
> +#define  PM8941_WLED_REG_SYNC_LED3             BIT(2)
> +#define  PM8941_WLED_REG_SYNC_ALL              0x07
> +#define  PM8941_WLED_REG_SYNC_CLEAR            0x00
> +
> +#define PM8941_WLED_REG_FREQ                   0x4c
> +#define  PM8941_WLED_REG_FREQ_MASK             0x0f
> +
> +#define PM8941_WLED_REG_OVP                    0x4d
> +#define  PM8941_WLED_REG_OVP_MASK              0x03
> +
> +#define PM8941_WLED_REG_BOOST                  0x4e
> +#define  PM8941_WLED_REG_BOOST_MASK            0x07
> +
> +#define PM8941_WLED_REG_SINK                   0x4f
> +#define  PM8941_WLED_REG_SINK_MASK             0xe0
> +#define  PM8941_WLED_REG_SINK_SHFT             0x05
> +
> +/* Per-'string' registers below */
> +#define PM8941_WLED_REG_STR_OFFSET             0x10
> +
> +#define PM8941_WLED_REG_STR_MOD_EN_BASE                0x60
> +#define  PM8941_WLED_REG_STR_MOD_MASK          BIT(7)
> +#define  PM8941_WLED_REG_STR_MOD_EN            BIT(7)
> +
> +#define PM8941_WLED_REG_STR_SCALE_BASE         0x62
> +#define  PM8941_WLED_REG_STR_SCALE_MASK                0x1f
> +
> +#define PM8941_WLED_REG_STR_MOD_SRC_BASE       0x63
> +#define  PM8941_WLED_REG_STR_MOD_SRC_MASK      0x01
> +#define  PM8941_WLED_REG_STR_MOD_SRC_INT       0x00
> +#define  PM8941_WLED_REG_STR_MOD_SRC_EXT       0x01
> +
> +#define PM8941_WLED_REG_STR_CABC_BASE          0x66
> +#define  PM8941_WLED_REG_STR_CABC_MASK         BIT(7)
> +#define  PM8941_WLED_REG_STR_CABC_EN           BIT(7)
> +
> +struct pm8941_wled_config {
> +       u32 i_boost_limit;
> +       u32 ovp;
> +       u32 switch_freq;
> +       u32 num_strings;
> +       u32 i_limit;
> +       bool cs_out_en;
> +       bool ext_gen;
> +       bool cabc_en;
> +};
> +
> +struct pm8941_wled {
> +       struct regmap *regmap;
> +       u16 addr;
> +
> +       struct led_classdev cdev;
> +
> +       struct pm8941_wled_config cfg;
> +};
> +
> +static int pm8941_wled_set(struct led_classdev *cdev,
> +                          enum led_brightness value)
> +{
> +       struct pm8941_wled *wled;
> +       u8 ctrl = 0;
> +       u16 val;
> +       int rc;
> +       int i;
> +
> +       wled = container_of(cdev, struct pm8941_wled, cdev);
> +
> +       if (value != 0)
> +               ctrl = PM8941_WLED_REG_MOD_EN_BIT;
> +
> +       val = value * PM8941_WLED_REG_VAL_MAX / LED_FULL;
> +
> +       rc = regmap_update_bits(wled->regmap,
> +                       wled->addr + PM8941_WLED_REG_MOD_EN,
> +                       PM8941_WLED_REG_MOD_EN_MASK, ctrl);
> +       if (rc)
> +               return rc;
> +
> +       for (i = 0; i < wled->cfg.num_strings; ++i) {
> +               u8 v[2] = { val & 0xff, (val >> 8) & 0xf };
> +
> +               rc = regmap_bulk_write(wled->regmap,
> +                               wled->addr + PM8941_WLED_REG_VAL_BASE + 2 * i,
> +                               v, 2);
> +               if (rc)
> +                       return rc;
> +       }
> +
> +       rc = regmap_update_bits(wled->regmap,
> +                       wled->addr + PM8941_WLED_REG_SYNC,
> +                       PM8941_WLED_REG_SYNC_MASK, PM8941_WLED_REG_SYNC_ALL);
> +       if (rc)
> +               return rc;
> +
> +       rc = regmap_update_bits(wled->regmap,
> +                       wled->addr + PM8941_WLED_REG_SYNC,
> +                       PM8941_WLED_REG_SYNC_MASK, PM8941_WLED_REG_SYNC_CLEAR);
> +       return rc;
> +}
> +
> +static void pm8941_wled_set_brightness(struct led_classdev *cdev,
> +                                      enum led_brightness value)
> +{
> +       if (pm8941_wled_set(cdev, value)) {
> +               dev_err(cdev->dev, "Unable to set brightness\n");
> +               return;
> +       }
> +       cdev->brightness = value;
> +}
> +
> +static int pm8941_wled_setup(struct pm8941_wled *wled)
> +{
> +       int rc;
> +       int i;
> +
> +       rc = regmap_update_bits(wled->regmap,
> +                       wled->addr + PM8941_WLED_REG_OVP,
> +                       PM8941_WLED_REG_OVP_MASK, wled->cfg.ovp);
> +       if (rc)
> +               return rc;
> +
> +       rc = regmap_update_bits(wled->regmap,
> +                       wled->addr + PM8941_WLED_REG_BOOST,
> +                       PM8941_WLED_REG_BOOST_MASK, wled->cfg.i_boost_limit);
> +       if (rc)
> +               return rc;
> +
> +       rc = regmap_update_bits(wled->regmap,
> +                       wled->addr + PM8941_WLED_REG_FREQ,
> +                       PM8941_WLED_REG_FREQ_MASK, wled->cfg.switch_freq);
> +       if (rc)
> +               return rc;
> +
> +       if (wled->cfg.cs_out_en) {
> +               u8 all = (BIT(wled->cfg.num_strings) - 1)
> +                               << PM8941_WLED_REG_SINK_SHFT;
> +
> +               rc = regmap_update_bits(wled->regmap,
> +                               wled->addr + PM8941_WLED_REG_SINK,
> +                               PM8941_WLED_REG_SINK_MASK, all);
> +               if (rc)
> +                       return rc;
> +       }
> +
> +       for (i = 0; i < wled->cfg.num_strings; ++i) {
> +               u16 addr = wled->addr + PM8941_WLED_REG_STR_OFFSET * i;
> +
> +               rc = regmap_update_bits(wled->regmap,
> +                               addr + PM8941_WLED_REG_STR_MOD_EN_BASE,
> +                               PM8941_WLED_REG_STR_MOD_MASK,
> +                               PM8941_WLED_REG_STR_MOD_EN);
> +               if (rc)
> +                       return rc;
> +
> +               if (wled->cfg.ext_gen) {
> +                       rc = regmap_update_bits(wled->regmap,
> +                                       addr + PM8941_WLED_REG_STR_MOD_SRC_BASE,
> +                                       PM8941_WLED_REG_STR_MOD_SRC_MASK,
> +                                       PM8941_WLED_REG_STR_MOD_SRC_EXT);
> +                       if (rc)
> +                               return rc;
> +               }
> +
> +               rc = regmap_update_bits(wled->regmap,
> +                               addr + PM8941_WLED_REG_STR_SCALE_BASE,
> +                               PM8941_WLED_REG_STR_SCALE_MASK,
> +                               wled->cfg.i_limit);
> +               if (rc)
> +                       return rc;
> +
> +               rc = regmap_update_bits(wled->regmap,
> +                               addr + PM8941_WLED_REG_STR_CABC_BASE,
> +                               PM8941_WLED_REG_STR_CABC_MASK,
> +                               wled->cfg.cabc_en ?
> +                                       PM8941_WLED_REG_STR_CABC_EN : 0);
> +               if (rc)
> +                       return rc;
> +       }
> +
> +       return 0;
> +}
> +
> +static const struct pm8941_wled_config pm8941_wled_config_defaults = {
> +       .i_boost_limit = 3,
> +       .i_limit = 20,
> +       .ovp = 2,
> +       .switch_freq = 5,
> +       .num_strings = 0,
> +       .cs_out_en = false,
> +       .ext_gen = false,
> +       .cabc_en = false,
> +};
> +
> +struct pm8941_wled_var_cfg {
> +       const u32 *values;
> +       u32 (*fn)(u32);
> +       int size;
> +};
> +
> +static const u32 pm8941_wled_i_boost_limit_values[] = {
> +       105, 385, 525, 805, 980, 1260, 1400, 1680,
> +};
> +
> +static const struct pm8941_wled_var_cfg pm8941_wled_i_boost_limit_cfg = {
> +       .values = pm8941_wled_i_boost_limit_values,
> +       .size = ARRAY_SIZE(pm8941_wled_i_boost_limit_values),
> +};
> +
> +static const u32 pm8941_wled_ovp_values[] = {
> +       35, 32, 29, 27,
> +};
> +
> +static const struct pm8941_wled_var_cfg pm8941_wled_ovp_cfg = {
> +       .values = pm8941_wled_ovp_values,
> +       .size = ARRAY_SIZE(pm8941_wled_ovp_values),
> +};
> +
> +static u32 pm8941_wled_num_strings_values_fn(u32 idx)
> +{
> +       return idx + 1;
> +}
> +
> +static const struct pm8941_wled_var_cfg pm8941_wled_num_strings_cfg = {
> +       .fn = pm8941_wled_num_strings_values_fn,
> +       .size = 3,
> +};
> +
> +static u32 pm8941_wled_switch_freq_values_fn(u32 idx)
> +{
> +       return 19200 / (2 * (1 + idx));
> +}
> +
> +static const struct pm8941_wled_var_cfg pm8941_wled_switch_freq_cfg = {
> +       .fn = pm8941_wled_switch_freq_values_fn,
> +       .size = 16,
> +};
> +
> +static const struct pm8941_wled_var_cfg pm8941_wled_i_limit_cfg = {
> +       .size = 26,
> +};
> +
> +static u32 pm8941_wled_values(const struct pm8941_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 pm8941_wled_configure(struct pm8941_wled *wled, struct device *dev)
> +{
> +       struct pm8941_wled_config *cfg = &wled->cfg;
> +       u32 val;
> +       int rc;
> +       u32 c;
> +       int i;
> +       int j;
> +
> +       const struct {
> +               const char *name;
> +               u32 *val_ptr;
> +               const struct pm8941_wled_var_cfg *cfg;
> +       } u32_opts[] = {
> +               {
> +                       "qcom,current-boost-limit",
> +                       &cfg->i_boost_limit,
> +                       .cfg = &pm8941_wled_i_boost_limit_cfg,
> +               },
> +               {
> +                       "qcom,current-limit",
> +                       &cfg->i_limit,
> +                       .cfg = &pm8941_wled_i_limit_cfg,
> +               },
> +               {
> +                       "qcom,ovp",
> +                       &cfg->ovp,
> +                       .cfg = &pm8941_wled_ovp_cfg,
> +               },
> +               {
> +                       "qcom,switching-freq",
> +                       &cfg->switch_freq,
> +                       .cfg = &pm8941_wled_switch_freq_cfg,
> +               },
> +               {
> +                       "qcom,num-strings",
> +                       &cfg->num_strings,
> +                       .cfg = &pm8941_wled_num_strings_cfg,
> +               },
> +       };
> +       const struct {
> +               const char *name;
> +               bool *val_ptr;
> +       } bool_opts[] = {
> +               { "qcom,cs-out", &cfg->cs_out_en, },
> +               { "qcom,ext-gen", &cfg->ext_gen, },
> +               { "qcom,cabc", &cfg->cabc_en, },
> +       };
> +
> +       rc = of_property_read_u32(dev->of_node, "reg", &val);
> +       if (rc || val > 0xffff) {
> +               dev_err(dev, "invalid IO resources\n");
> +               return rc ? rc : -EINVAL;
> +       }
> +       wled->addr = val;
> +
> +       rc = of_property_read_string(dev->of_node, "label", &wled->cdev.name);
> +       if (rc)
> +               wled->cdev.name = dev->of_node->name;
> +
> +       wled->cdev.default_trigger = of_get_property(dev->of_node,
> +                       "linux,default-trigger", NULL);
> +
> +       *cfg = pm8941_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) {
> +                       dev_err(dev, "error reading '%s'\n", u32_opts[i].name);
> +                       return rc;
> +               }
> +
> +               c = UINT_MAX;
> +               for (j = 0; c != val; j++) {
> +                       c = pm8941_wled_values(u32_opts[i].cfg, j);
> +                       if (c == UINT_MAX) {
> +                               dev_err(dev, "invalid value for '%s'\n",
> +                                       u32_opts[i].name);
> +                               return -EINVAL;
> +                       }
> +               }
> +
> +               dev_dbg(dev, "'%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;
> +       }
> +
> +       cfg->num_strings = cfg->num_strings + 1;
> +
> +       return 0;
> +}
> +
> +static int pm8941_wled_probe(struct platform_device *pdev)
> +{
> +       struct pm8941_wled *wled;
> +       struct regmap *regmap;
> +       int rc;
> +
> +       regmap = dev_get_regmap(pdev->dev.parent, NULL);
> +       if (!regmap) {
> +               dev_err(&pdev->dev, "Unable to get regmap\n");
> +               return -EINVAL;
> +       }
> +
> +       wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL);
> +       if (!wled)
> +               return -ENOMEM;
> +
> +       wled->regmap = regmap;
> +
> +       rc = pm8941_wled_configure(wled, &pdev->dev);
> +       if (rc)
> +               return rc;
> +
> +       rc = pm8941_wled_setup(wled);
> +       if (rc)
> +               return rc;
> +
> +       wled->cdev.brightness_set = pm8941_wled_set_brightness;
> +
> +       rc = led_classdev_register(&pdev->dev, &wled->cdev);

Can you replace this with your devm_led_classdev_register()? Since
it's  merged already, I will ask people to use it for new LED drivers.

Thanks,
-Bryan

> +       if (rc)
> +               return rc;
> +
> +       platform_set_drvdata(pdev, wled);
> +
> +       return 0;
> +};
> +
> +static int pm8941_wled_remove(struct platform_device *pdev)
> +{
> +       struct pm8941_wled *wled;
> +
> +       wled = platform_get_drvdata(pdev);
> +       led_classdev_unregister(&wled->cdev);
> +
> +       return 0;
> +}
> +
> +static const struct of_device_id pm8941_wled_match_table[] = {
> +       { .compatible = "qcom,pm8941-wled" },
> +       {}
> +};
> +MODULE_DEVICE_TABLE(of, pm8941_wled_match_table);
> +
> +static struct platform_driver pm8941_wled_driver = {
> +       .probe = pm8941_wled_probe,
> +       .remove = pm8941_wled_remove,
> +       .driver = {
> +               .name = "pm8941-wled",
> +               .of_match_table = pm8941_wled_match_table,
> +       },
> +};
> +
> +module_platform_driver(pm8941_wled_driver);
> +
> +MODULE_DESCRIPTION("pm8941 wled driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:pm8941-wled");
> --
> 1.9.1
>
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]
  Powered by Linux