leds: leds-qpnp-flash-v2: create v2 QPNP flash LED driver
There is a new Qualcomm Technology Inc. Plug-n-play(QPNP) PMIC chip, which introduces brand new flash LED hardware. The new hardware comes with up to 3 LEDs support, different register mapping layout, and different torch enablement requirement. Therefore, a new driver is introduced to cover this need. Change-Id: Ic878f1a946955edff3a9228e7fe54b7a525e37b1 Signed-off-by: Chun Zhang <chunz@codeaurora.org> Signed-off-by: Mohan Pallaka <mpallaka@codeaurora.org>
This commit is contained in:
parent
9be27fb6e7
commit
c7ff58f93f
5 changed files with 847 additions and 0 deletions
153
Documentation/devicetree/bindings/leds/leds-qpnp-flash-v2.txt
Normal file
153
Documentation/devicetree/bindings/leds/leds-qpnp-flash-v2.txt
Normal file
|
@ -0,0 +1,153 @@
|
|||
Qualcomm Technologies Inc. PNP v2 Flash LED
|
||||
|
||||
QPNP (Qualcomm Technologies Inc. Plug N Play) Flash LED (Light
|
||||
Emitting Diode) driver v2 is used to provide illumination to
|
||||
camera sensor when background light is dim to capture good
|
||||
picture. It can also be used for flashlight/torch application.
|
||||
It is part of PMIC on Qualcomm Technologies Inc. reference platforms.
|
||||
|
||||
Required properties:
|
||||
- compatible : Should be "qcom,qpnp-flash-led-v2"
|
||||
- reg : Base address and size for flash LED modules
|
||||
|
||||
Optional properties:
|
||||
- qcom,hdrm-auto-mode : Boolean type to select headroom auto mode enabled or not
|
||||
- qcom,isc-delay : Integer type to specify short circuit delay. Valid values are 32, 64,
|
||||
128, 192. Unit is us.
|
||||
|
||||
Required properties inside child node. Child node contains settings for each individual LED.
|
||||
Each LED channel needs a flash node and torch node for itself, and an individual switch node to
|
||||
serve as an overall switch.
|
||||
- label : Type of led that will be used, either "flash", "torch", or "switch.
|
||||
- qcom,led-name : Name of the LED.
|
||||
- qcom,default-led-trigger : Trigger for the camera flash and torch. Accepted values are
|
||||
"flash0_trigger", "flash1_trigger", "flash2_trigger, "torch0_trigger",
|
||||
"torch1_trigger", "torch2_trigger", and "switch_trigger".
|
||||
- qcom,id : ID for each physical LED equipped. In order to handle situation when
|
||||
only 1 or 2 LEDs are installed, flash and torch nodes on LED channel 0
|
||||
should be specified with ID 0; nodes on channel 1 be ID 1, etc. This is
|
||||
not required for switch node.
|
||||
- qcom,max-current : Maximum current allowed on this LED. Valid values should be
|
||||
integer from 0 to 1500 inclusive. Flash 2 should have maximum current of
|
||||
750 per hardware requirement. Unit is mA. This is not required for switch
|
||||
node.
|
||||
- qcom,duration-ms : Required property for flash nodes but not needed for torch. Integer
|
||||
type specifying flash duration. Values are from 10ms to 1280ms with
|
||||
10ms resolution. This is not required for switch node.
|
||||
|
||||
Optional properties inside child node:
|
||||
- qcom,ires-ua : Integer type to specify current resolution. Accepted values should be
|
||||
12500, 10000, 7500, and 5000. Unit is uA.
|
||||
- qcom,hdrm-voltage-mv : Integer type specifying headroom voltage. Values are from 125mV to 500mV
|
||||
with 25mV resolution. Default setting is 325mV
|
||||
- qcom,hdrm-vol-hi-lo-win-mv : Integer type to specify headroom voltage swing range. Values are
|
||||
from 0mV to 375mV with 25mV resolution. Default setting is 100mV.
|
||||
- pinctrl-names : This should be defined if a target uses pinctrl framework and there is GPIO
|
||||
requirement for flash LEDs. See "pinctrl" in
|
||||
Documentation/devicetree/bindings/pinctrl/msm-pinctrl.txt. It should specify
|
||||
the names of the configs that pinctrl can install in driver.
|
||||
Following are the pinctrl configs that can be installed:
|
||||
"led_enable" : Enablement configuration of pins. This should specify active
|
||||
config defined in each pin or pin group.
|
||||
"led_disable" : Disablement configuration of pins. This should specify inactive
|
||||
config defined in each pin or pin groups.
|
||||
|
||||
Example:
|
||||
qcom,leds@d300 {
|
||||
compatible = "qcom,qpnp-flash-led-v2";
|
||||
status = "okay";
|
||||
reg = <0xd300 0x100>;
|
||||
label = "flash";
|
||||
|
||||
qcom,hdrm-auto-mode;
|
||||
qcom,isc-delay = <192>;
|
||||
|
||||
pmi8998_flash0: qcom,flash_0 {
|
||||
label = "flash";
|
||||
qcom,led-name = "led:flash_0";
|
||||
qcom,max-current = <1500>;
|
||||
qcom,default-led-trigger =
|
||||
"flash0_trigger";
|
||||
qcom,id = <0>;
|
||||
qcom,duration-ms = <1280>;
|
||||
qcom,ires-ua = <12500>;
|
||||
qcom,hdrm-voltage-mv = <325>;
|
||||
qcom,hdrm-vol-hi-lo-win-mv = <100>;
|
||||
};
|
||||
|
||||
pmi8998_flash1: qcom,flash_1 {
|
||||
label = "flash";
|
||||
qcom,led-name = "led:flash_1";
|
||||
qcom,max-current = <1500>;
|
||||
qcom,default-led-trigger =
|
||||
"flash1_trigger";
|
||||
qcom,id = <1>;
|
||||
qcom,duration-ms = <1280>;
|
||||
qcom,ires-ua = <12500>;
|
||||
qcom,hdrm-voltage-mv = <325>;
|
||||
qcom,hdrm-vol-hi-lo-win-mv = <100>;
|
||||
};
|
||||
|
||||
pmi8998_flash2: qcom,flash_2 {
|
||||
label = "flash";
|
||||
qcom,led-name = "led:flash_2";
|
||||
qcom,max-current = <750>;
|
||||
qcom,default-led-trigger =
|
||||
"flash2_trigger";
|
||||
qcom,id = <2>;
|
||||
qcom,duration-ms = <1280>;
|
||||
qcom,ires-ua = <12500>;
|
||||
qcom,hdrm-voltage-mv = <325>;
|
||||
qcom,hdrm-vol-hi-lo-win-mv = <100>;
|
||||
pinctrl-names = "led_enable","led_disable";
|
||||
pinctrl-0 = <&led_enable>;
|
||||
pinctrl-1 = <&led_disable>;
|
||||
};
|
||||
|
||||
pmi8998_torch0: qcom,torch_0 {
|
||||
label = "torch";
|
||||
qcom,led-name = "led:torch_0";
|
||||
qcom,max-current = <200>;
|
||||
qcom,default-led-trigger =
|
||||
"torch0_trigger";
|
||||
qcom,id = <0>;
|
||||
qcom,ires-ua = <12500>;
|
||||
qcom,hdrm-voltage-mv = <325>;
|
||||
qcom,hdrm-vol-hi-lo-win-mv = <100>;
|
||||
};
|
||||
|
||||
pmi8998_torch1: qcom,torch_1 {
|
||||
label = "torch";
|
||||
qcom,led-name = "led:torch_1";
|
||||
qcom,max-current = <200>;
|
||||
qcom,default-led-trigger =
|
||||
"torch1_trigger";
|
||||
qcom,id = <1>;
|
||||
qcom,ires-ua = <12500>;
|
||||
qcom,hdrm-voltage-mv = <325>;
|
||||
qcom,hdrm-vol-hi-lo-win-mv = <100>;
|
||||
};
|
||||
|
||||
pmi8998_torch2: qcom,torch_2 {
|
||||
label = "torch";
|
||||
qcom,led-name = "led:torch_2";
|
||||
qcom,max-current = <200>;
|
||||
qcom,default-led-trigger =
|
||||
"torch2_trigger";
|
||||
qcom,id = <2>;
|
||||
qcom,ires-ua = <12500>;
|
||||
qcom,hdrm-voltage-mv = <325>;
|
||||
qcom,hdrm-vol-hi-lo-win-mv = <100>;
|
||||
pinctrl-names = "led_enable","led_disable";
|
||||
pinctrl-0 = <&led_enable>;
|
||||
pinctrl-1 = <&led_disable>;
|
||||
};
|
||||
|
||||
pmi8998_switch: qcom,led_switch {
|
||||
label = "switch";
|
||||
qcom,led-name = "led:switch";
|
||||
qcom,default-led-trigger =
|
||||
"switch_trigger";
|
||||
};
|
||||
};
|
||||
|
|
@ -602,6 +602,16 @@ config LEDS_QPNP_FLASH
|
|||
To compile this driver as a module, choose M here: the module will
|
||||
be called leds-qpnp-flash.
|
||||
|
||||
config LEDS_QPNP_FLASH_V2
|
||||
tristate "Support for QPNP V2 Flash LEDs"
|
||||
depends on LEDS_CLASS && MFD_SPMI_PMIC
|
||||
help
|
||||
This driver supports the leds functionality of Qualcomm Technologies
|
||||
PNP PMIC. It includes Flash Led.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called leds-qpnp-flash-v2.
|
||||
|
||||
config LEDS_QPNP_WLED
|
||||
tristate "Support for QPNP WLED"
|
||||
depends on LEDS_CLASS && SPMI
|
||||
|
|
|
@ -62,6 +62,7 @@ obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o
|
|||
obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
|
||||
obj-$(CONFIG_LEDS_QPNP) += leds-qpnp.o
|
||||
obj-$(CONFIG_LEDS_QPNP_FLASH) += leds-qpnp-flash.o
|
||||
obj-$(CONFIG_LEDS_QPNP_FLASH_V2) += leds-qpnp-flash-v2.o
|
||||
obj-$(CONFIG_LEDS_QPNP_WLED) += leds-qpnp-wled.o
|
||||
obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o
|
||||
obj-$(CONFIG_LEDS_VERSATILE) += leds-versatile.o
|
||||
|
|
639
drivers/leds/leds-qpnp-flash-v2.c
Normal file
639
drivers/leds/leds-qpnp-flash-v2.c
Normal file
|
@ -0,0 +1,639 @@
|
|||
/* Copyright (c) 2016, 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/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/leds-qpnp-flash-v2.h>
|
||||
|
||||
#define FLASH_LED_REG_SAFETY_TMR(base) (base + 0x40)
|
||||
#define FLASH_LED_REG_TGR_CURRENT(base) (base + 0x43)
|
||||
#define FLASH_LED_REG_MOD_CTRL(base) (base + 0x46)
|
||||
#define FLASH_LED_REG_IRES(base) (base + 0x47)
|
||||
#define FLASH_LED_REG_STROBE_CTRL(base) (base + 0x49)
|
||||
#define FLASH_LED_REG_CHANNEL_CTRL(base) (base + 0x4C)
|
||||
#define FLASH_LED_REG_HDRM_PRGM(base) (base + 0x4D)
|
||||
#define FLASH_LED_REG_HDRM_AUTO_MODE_CTRL(base) (base + 0x50)
|
||||
#define FLASH_LED_REG_ISC_DELAY(base) (base + 0x52)
|
||||
|
||||
#define FLASH_LED_HDRM_MODE_PRGM_MASK 0xFF
|
||||
#define FLASH_LED_HDRM_VOL_MASK 0xF0
|
||||
#define FLASH_LED_CURRENT_MASK 0x3F
|
||||
#define FLASH_LED_STROBE_CTRL_MASK 0x07
|
||||
#define FLASH_LED_SAFETY_TMR_MASK_MASK 0x7F
|
||||
#define FLASH_LED_MOD_CTRL_MASK 0x80
|
||||
#define FLASH_LED_ISC_DELAY_MASK 0x03
|
||||
|
||||
#define FLASH_LED_TYPE_FLASH 0
|
||||
#define FLASH_LED_TYPE_TORCH 1
|
||||
#define FLASH_LED_HEADROOM_AUTO_MODE_ENABLED true
|
||||
#define FLASH_LED_ISC_DELAY_SHIFT 6
|
||||
#define FLASH_LED_ISC_DELAY_DEFAULT_US 3
|
||||
#define FLASH_LED_SAFETY_TMR_VAL_OFFSET 1
|
||||
#define FLASH_LED_SAFETY_TMR_VAL_DIVISOR 10
|
||||
#define FLASH_LED_SAFETY_TMR_ENABLED 0x08
|
||||
#define FLASH_LED_IRES_BASE 3
|
||||
#define FLASH_LED_IRES_DIVISOR 2500
|
||||
#define FLASH_LED_IRES_MIN_UA 5000
|
||||
#define FLASH_LED_IRES_DEFAULT_UA 12500
|
||||
#define FLASH_LED_IRES_DEFAULT_VAL 0x00
|
||||
#define FLASH_LED_HDRM_VOL_SHIFT 4
|
||||
#define FLASH_LED_HDRM_VOL_DEFAULT_MV 0x80
|
||||
#define FLASH_LED_HDRM_VOL_HI_LO_WIN_DEFAULT_MV 0x04
|
||||
#define FLASH_LED_HDRM_VOL_BASE_MV 125
|
||||
#define FLASH_LED_HDRM_VOL_STEP_MV 25
|
||||
#define FLASH_LED_STROBE_ENABLE 0x01
|
||||
#define FLASH_LED_MOD_ENABLE 0x80
|
||||
#define FLASH_LED_DISABLE 0x00
|
||||
#define FLASH_LED_SAFETY_TMR_DISABLED 0x13
|
||||
|
||||
/*
|
||||
* Flash LED configuration read from device tree
|
||||
*/
|
||||
struct flash_led_platform_data {
|
||||
u8 isc_delay_us;
|
||||
bool hdrm_auto_mode_en;
|
||||
};
|
||||
|
||||
/*
|
||||
* Flash LED data structure containing flash LED attributes
|
||||
*/
|
||||
struct qpnp_flash_led {
|
||||
struct flash_led_platform_data *pdata;
|
||||
struct platform_device *pdev;
|
||||
struct regmap *regmap;
|
||||
struct flash_node_data *fnode;
|
||||
struct flash_switch_data *snode;
|
||||
spinlock_t lock;
|
||||
int num_led_nodes;
|
||||
int num_avail_leds;
|
||||
u16 base;
|
||||
};
|
||||
|
||||
static int
|
||||
qpnp_flash_led_masked_write(struct qpnp_flash_led *led, u16 addr, u8 mask,
|
||||
u8 val)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = regmap_update_bits(led->regmap, addr, mask, val);
|
||||
if (rc)
|
||||
dev_err(&led->pdev->dev,
|
||||
"Unable to update bits from 0x%02X, rc = %d\n",
|
||||
addr, rc);
|
||||
|
||||
dev_dbg(&led->pdev->dev, "Write 0x%02X to addr 0x%02X\n", val, addr);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static enum
|
||||
led_brightness qpnp_flash_led_brightness_get(struct led_classdev *led_cdev)
|
||||
{
|
||||
return led_cdev->brightness;
|
||||
}
|
||||
|
||||
static int qpnp_flash_led_init_settings(struct qpnp_flash_led *led)
|
||||
{
|
||||
int rc, i, addr_offset;
|
||||
u8 val = 0;
|
||||
|
||||
for (i = 0; i < led->num_avail_leds; i++) {
|
||||
addr_offset = led->fnode[i].id;
|
||||
rc = qpnp_flash_led_masked_write(led,
|
||||
FLASH_LED_REG_HDRM_PRGM(led->base + addr_offset),
|
||||
FLASH_LED_HDRM_MODE_PRGM_MASK,
|
||||
led->fnode[i].hdrm_val);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
val |= 0x1 << led->fnode[i].id;
|
||||
}
|
||||
|
||||
rc = qpnp_flash_led_masked_write(led,
|
||||
FLASH_LED_REG_HDRM_AUTO_MODE_CTRL(led->base),
|
||||
FLASH_LED_HDRM_MODE_PRGM_MASK, val);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = qpnp_flash_led_masked_write(led,
|
||||
FLASH_LED_REG_ISC_DELAY(led->base),
|
||||
FLASH_LED_ISC_DELAY_MASK, led->pdata->isc_delay_us);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void qpnp_flash_led_node_set(struct flash_node_data *fnode, int value)
|
||||
{
|
||||
int prgm_current_ma;
|
||||
|
||||
prgm_current_ma = value < 0 ? 0 : value;
|
||||
prgm_current_ma = value > fnode->cdev.max_brightness ?
|
||||
fnode->cdev.max_brightness : value;
|
||||
fnode->cdev.brightness = prgm_current_ma;
|
||||
fnode->brightness = prgm_current_ma * 1000 / fnode->ires_ua + 1;
|
||||
fnode->led_on = prgm_current_ma != 0;
|
||||
}
|
||||
|
||||
static int qpnp_flash_led_switch_set(struct flash_switch_data *snode, bool on)
|
||||
{
|
||||
struct qpnp_flash_led *led = dev_get_drvdata(&snode->pdev->dev);
|
||||
int rc, i, addr_offset;
|
||||
u8 val;
|
||||
|
||||
if (!on)
|
||||
goto leds_turn_off;
|
||||
|
||||
val = 0;
|
||||
for (i = 0; i < led->num_led_nodes; i++)
|
||||
val |= led->fnode[i].ires << (led->fnode[i].id * 2);
|
||||
rc = qpnp_flash_led_masked_write(led, FLASH_LED_REG_IRES(led->base),
|
||||
FLASH_LED_CURRENT_MASK, val);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
val = 0;
|
||||
for (i = 0; i < led->num_avail_leds; i++) {
|
||||
if (!led->fnode[i].led_on)
|
||||
continue;
|
||||
|
||||
addr_offset = led->fnode[i].id;
|
||||
rc = qpnp_flash_led_masked_write(led,
|
||||
FLASH_LED_REG_STROBE_CTRL(led->base + addr_offset),
|
||||
FLASH_LED_STROBE_CTRL_MASK, FLASH_LED_STROBE_ENABLE);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = qpnp_flash_led_masked_write(led,
|
||||
FLASH_LED_REG_TGR_CURRENT(led->base + addr_offset),
|
||||
FLASH_LED_CURRENT_MASK, led->fnode[i].brightness);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = qpnp_flash_led_masked_write(led,
|
||||
FLASH_LED_REG_SAFETY_TMR(led->base + addr_offset),
|
||||
FLASH_LED_SAFETY_TMR_MASK_MASK, led->fnode[i].duration);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
val |= FLASH_LED_STROBE_ENABLE << led->fnode[i].id;
|
||||
|
||||
if (led->fnode[i].pinctrl) {
|
||||
rc = pinctrl_select_state(led->fnode[i].pinctrl,
|
||||
led->fnode[i].gpio_state_active);
|
||||
if (rc) {
|
||||
dev_err(&led->pdev->dev,
|
||||
"failed to enable GPIO\n");
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rc = qpnp_flash_led_masked_write(led, FLASH_LED_REG_MOD_CTRL(led->base),
|
||||
FLASH_LED_MOD_CTRL_MASK, FLASH_LED_MOD_ENABLE);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = qpnp_flash_led_masked_write(led,
|
||||
FLASH_LED_REG_CHANNEL_CTRL(led->base),
|
||||
FLASH_LED_STROBE_CTRL_MASK, val);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
return 0;
|
||||
|
||||
leds_turn_off:
|
||||
rc = qpnp_flash_led_masked_write(led,
|
||||
FLASH_LED_REG_CHANNEL_CTRL(led->base),
|
||||
FLASH_LED_STROBE_CTRL_MASK, FLASH_LED_DISABLE);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = qpnp_flash_led_masked_write(led, FLASH_LED_REG_MOD_CTRL(led->base),
|
||||
FLASH_LED_MOD_CTRL_MASK, FLASH_LED_DISABLE);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
for (i = 0; i < led->num_led_nodes; i++) {
|
||||
if (!led->fnode[i].led_on)
|
||||
continue;
|
||||
|
||||
addr_offset = led->fnode[i].id;
|
||||
rc = qpnp_flash_led_masked_write(led,
|
||||
FLASH_LED_REG_TGR_CURRENT(led->base + addr_offset),
|
||||
FLASH_LED_CURRENT_MASK, 0);
|
||||
if (rc)
|
||||
return rc;
|
||||
led->fnode[i].led_on = false;
|
||||
|
||||
if (led->fnode[i].pinctrl) {
|
||||
rc = pinctrl_select_state(led->fnode[i].pinctrl,
|
||||
led->fnode[i].gpio_state_suspend);
|
||||
if (rc) {
|
||||
dev_err(&led->pdev->dev,
|
||||
"failed to disable GPIO\n");
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void qpnp_flash_led_brightness_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct flash_node_data *fnode = NULL;
|
||||
struct flash_switch_data *snode = NULL;
|
||||
struct qpnp_flash_led *led = dev_get_drvdata(&fnode->pdev->dev);
|
||||
int rc;
|
||||
|
||||
if (!strcmp(led_cdev->name, "led:switch")) {
|
||||
snode = container_of(led_cdev, struct flash_switch_data, cdev);
|
||||
led = dev_get_drvdata(&snode->pdev->dev);
|
||||
} else {
|
||||
fnode = container_of(led_cdev, struct flash_node_data, cdev);
|
||||
led = dev_get_drvdata(&fnode->pdev->dev);
|
||||
}
|
||||
|
||||
spin_lock(&led->lock);
|
||||
if (!fnode) {
|
||||
rc = qpnp_flash_led_switch_set(snode, value > 0);
|
||||
if (rc) {
|
||||
dev_err(&led->pdev->dev,
|
||||
"Failed to set flash LED switch\n");
|
||||
goto exit;
|
||||
}
|
||||
} else {
|
||||
qpnp_flash_led_node_set(fnode, value);
|
||||
}
|
||||
|
||||
exit:
|
||||
spin_unlock(&led->lock);
|
||||
}
|
||||
|
||||
static int qpnp_flash_led_parse_each_led_dt(struct qpnp_flash_led *led,
|
||||
struct flash_node_data *fnode, struct device_node *node)
|
||||
{
|
||||
const char *temp_string;
|
||||
int rc;
|
||||
u32 val;
|
||||
|
||||
fnode->pdev = led->pdev;
|
||||
fnode->cdev.brightness_set = qpnp_flash_led_brightness_set;
|
||||
fnode->cdev.brightness_get = qpnp_flash_led_brightness_get;
|
||||
|
||||
rc = of_property_read_string(node, "qcom,led-name", &fnode->cdev.name);
|
||||
if (rc) {
|
||||
dev_err(&led->pdev->dev, "Unable to read flash LED names\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32(node, "qcom,max-current", &val);
|
||||
if (!rc) {
|
||||
fnode->cdev.max_brightness = val;
|
||||
} else {
|
||||
dev_err(&led->pdev->dev, "Unable to read max current\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = of_property_read_string(node, "label", &temp_string);
|
||||
if (!rc) {
|
||||
if (!strcmp(temp_string, "flash"))
|
||||
fnode->type = FLASH_LED_TYPE_FLASH;
|
||||
else if (!strcmp(temp_string, "torch"))
|
||||
fnode->type = FLASH_LED_TYPE_TORCH;
|
||||
else {
|
||||
dev_err(&led->pdev->dev, "Wrong flash LED type\n");
|
||||
return rc;
|
||||
}
|
||||
} else {
|
||||
dev_err(&led->pdev->dev, "Unable to read flash LED label\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32(node, "qcom,id", &val);
|
||||
if (!rc) {
|
||||
fnode->id = (u8)val;
|
||||
} else {
|
||||
dev_err(&led->pdev->dev, "Unable to read flash LED ID\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = of_property_read_string(node, "qcom,default-led-trigger",
|
||||
&fnode->cdev.default_trigger);
|
||||
if (rc) {
|
||||
dev_err(&led->pdev->dev, "Unable to read trigger name\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
fnode->ires_ua = FLASH_LED_IRES_DEFAULT_UA;
|
||||
fnode->ires = FLASH_LED_IRES_DEFAULT_VAL;
|
||||
rc = of_property_read_u32(node, "qcom,ires-ua", &val);
|
||||
if (!rc) {
|
||||
fnode->ires_ua = val;
|
||||
fnode->ires = FLASH_LED_IRES_BASE -
|
||||
(val - FLASH_LED_IRES_MIN_UA) / FLASH_LED_IRES_DIVISOR;
|
||||
} else if (rc != -EINVAL) {
|
||||
dev_err(&led->pdev->dev, "Unable to read current resolution\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
fnode->duration = FLASH_LED_SAFETY_TMR_DISABLED;
|
||||
rc = of_property_read_u32(node, "qcom,duration-ms", &val);
|
||||
if (!rc) {
|
||||
fnode->duration = (u8)(((val -
|
||||
FLASH_LED_SAFETY_TMR_VAL_OFFSET) /
|
||||
FLASH_LED_SAFETY_TMR_VAL_DIVISOR) |
|
||||
FLASH_LED_SAFETY_TMR_ENABLED);
|
||||
} else if (rc == -EINVAL) {
|
||||
if (fnode->type == FLASH_LED_TYPE_FLASH) {
|
||||
dev_err(&led->pdev->dev,
|
||||
"Timer duration is required for flash LED\n");
|
||||
return rc;
|
||||
}
|
||||
} else {
|
||||
dev_err(&led->pdev->dev,
|
||||
"Unable to read timer duration\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
fnode->hdrm_val = FLASH_LED_HDRM_VOL_DEFAULT_MV;
|
||||
rc = of_property_read_u32(node, "qcom,hdrm-voltage-mv", &val);
|
||||
if (!rc) {
|
||||
val = (val - FLASH_LED_HDRM_VOL_BASE_MV) /
|
||||
FLASH_LED_HDRM_VOL_STEP_MV;
|
||||
fnode->hdrm_val = (val << FLASH_LED_HDRM_VOL_SHIFT) &
|
||||
FLASH_LED_HDRM_VOL_MASK;
|
||||
} else if (rc != -EINVAL) {
|
||||
dev_err(&led->pdev->dev, "Unable to read headroom voltage\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32(node, "qcom,hdrm-vol-hi-lo-win-mv", &val);
|
||||
if (!rc) {
|
||||
fnode->hdrm_val |= (val / FLASH_LED_HDRM_VOL_STEP_MV) &
|
||||
~FLASH_LED_HDRM_VOL_MASK;
|
||||
} else if (rc == -EINVAL) {
|
||||
fnode->hdrm_val |= FLASH_LED_HDRM_VOL_HI_LO_WIN_DEFAULT_MV;
|
||||
} else {
|
||||
dev_err(&led->pdev->dev,
|
||||
"Unable to read hdrm hi-lo window voltage\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = led_classdev_register(&led->pdev->dev, &fnode->cdev);
|
||||
if (rc) {
|
||||
dev_err(&led->pdev->dev, "Unable to register led node %d\n",
|
||||
fnode->id);
|
||||
return rc;
|
||||
}
|
||||
fnode->cdev.dev->of_node = node;
|
||||
|
||||
fnode->pinctrl = devm_pinctrl_get(fnode->cdev.dev);
|
||||
if (IS_ERR_OR_NULL(fnode->pinctrl)) {
|
||||
dev_err(&led->pdev->dev, "No pinctrl defined\n");
|
||||
fnode->pinctrl = NULL;
|
||||
} else {
|
||||
fnode->gpio_state_active =
|
||||
pinctrl_lookup_state(fnode->pinctrl, "led_enable");
|
||||
if (IS_ERR_OR_NULL(fnode->gpio_state_active)) {
|
||||
dev_err(&led->pdev->dev,
|
||||
"Cannot lookup LED active state\n");
|
||||
devm_pinctrl_put(fnode->pinctrl);
|
||||
fnode->pinctrl = NULL;
|
||||
return PTR_ERR(fnode->gpio_state_active);
|
||||
}
|
||||
|
||||
fnode->gpio_state_suspend =
|
||||
pinctrl_lookup_state(fnode->pinctrl, "led_disable");
|
||||
if (IS_ERR_OR_NULL(fnode->gpio_state_suspend)) {
|
||||
dev_err(&led->pdev->dev,
|
||||
"Cannot lookup LED disable state\n");
|
||||
devm_pinctrl_put(fnode->pinctrl);
|
||||
fnode->pinctrl = NULL;
|
||||
return PTR_ERR(fnode->gpio_state_suspend);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qpnp_flash_led_parse_and_register_switch(struct qpnp_flash_led *led,
|
||||
struct device_node *node)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = of_property_read_string(node, "qcom,led-name",
|
||||
&led->snode->cdev.name);
|
||||
if (rc) {
|
||||
dev_err(&led->pdev->dev, "Failed to read switch node name\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = of_property_read_string(node, "qcom,default-led-trigger",
|
||||
&led->snode->cdev.default_trigger);
|
||||
if (rc) {
|
||||
dev_err(&led->pdev->dev, "Unable to read trigger name\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
led->snode->pdev = led->pdev;
|
||||
led->snode->cdev.brightness_set = qpnp_flash_led_brightness_set;
|
||||
led->snode->cdev.brightness_get = qpnp_flash_led_brightness_get;
|
||||
rc = led_classdev_register(&led->pdev->dev, &led->snode->cdev);
|
||||
if (rc) {
|
||||
dev_err(&led->pdev->dev,
|
||||
"Unable to register led switch node\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qpnp_flash_led_parse_common_dt(struct qpnp_flash_led *led,
|
||||
struct device_node *node)
|
||||
{
|
||||
int rc;
|
||||
u32 val;
|
||||
|
||||
led->pdata->hdrm_auto_mode_en = FLASH_LED_HEADROOM_AUTO_MODE_ENABLED;
|
||||
led->pdata->hdrm_auto_mode_en = of_property_read_bool(node,
|
||||
"qcom,hdrm-auto-mode");
|
||||
|
||||
led->pdata->isc_delay_us = FLASH_LED_ISC_DELAY_DEFAULT_US;
|
||||
rc = of_property_read_u32(node, "qcom,isc-delay", &val);
|
||||
if (!rc) {
|
||||
led->pdata->isc_delay_us = val >> FLASH_LED_ISC_DELAY_SHIFT;
|
||||
} else if (rc != -EINVAL) {
|
||||
dev_err(&led->pdev->dev, "Unable to read ISC delay\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qpnp_flash_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct qpnp_flash_led *led;
|
||||
struct device_node *node, *temp;
|
||||
unsigned int base;
|
||||
int rc, i = 0;
|
||||
|
||||
node = pdev->dev.of_node;
|
||||
if (!node) {
|
||||
dev_info(&pdev->dev, "No flash LED nodes defined\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32(node, "reg", &base);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "Couldn't find reg in node %s, rc = %d\n",
|
||||
node->full_name, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
led = devm_kzalloc(&pdev->dev, sizeof(struct qpnp_flash_led),
|
||||
GFP_KERNEL);
|
||||
if (!led)
|
||||
return -ENOMEM;
|
||||
|
||||
led->regmap = dev_get_regmap(pdev->dev.parent, NULL);
|
||||
if (!led->regmap) {
|
||||
dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
led->base = base;
|
||||
led->pdev = pdev;
|
||||
led->pdata = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct flash_led_platform_data), GFP_KERNEL);
|
||||
if (!led->pdata)
|
||||
return -ENOMEM;
|
||||
|
||||
rc = qpnp_flash_led_parse_common_dt(led, node);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev,
|
||||
"Failed to parse common flash LED device tree\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
for_each_child_of_node(node, temp)
|
||||
led->num_led_nodes++;
|
||||
if (!led->num_led_nodes) {
|
||||
dev_err(&pdev->dev, "No LED nodes defined\n");
|
||||
return -ECHILD;
|
||||
}
|
||||
|
||||
led->fnode = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct flash_node_data) * (--led->num_led_nodes),
|
||||
GFP_KERNEL);
|
||||
if (!led->fnode)
|
||||
return -ENOMEM;
|
||||
|
||||
temp = NULL;
|
||||
for (i = 0; i < led->num_led_nodes; i++) {
|
||||
temp = of_get_next_child(node, temp);
|
||||
rc = qpnp_flash_led_parse_each_led_dt(led,
|
||||
&led->fnode[i], temp);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev,
|
||||
"Unable to parse flash node %d\n", i);
|
||||
goto error_led_register;
|
||||
}
|
||||
}
|
||||
led->num_avail_leds = i;
|
||||
|
||||
led->snode = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct flash_switch_data), GFP_KERNEL);
|
||||
if (!led->snode) {
|
||||
rc = -ENOMEM;
|
||||
goto error_led_register;
|
||||
}
|
||||
|
||||
temp = of_get_next_child(node, temp);
|
||||
rc = qpnp_flash_led_parse_and_register_switch(led, temp);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev,
|
||||
"Unable to parse and register switch node\n");
|
||||
goto error_led_register;
|
||||
}
|
||||
|
||||
rc = qpnp_flash_led_init_settings(led);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "Failed to initialize flash LED\n");
|
||||
goto error_switch_register;
|
||||
}
|
||||
|
||||
spin_lock_init(&led->lock);
|
||||
|
||||
dev_set_drvdata(&pdev->dev, led);
|
||||
|
||||
return 0;
|
||||
|
||||
error_switch_register:
|
||||
led_classdev_unregister(&led->snode->cdev);
|
||||
error_led_register:
|
||||
while (i > 0)
|
||||
led_classdev_unregister(&led->fnode[--i].cdev);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int qpnp_flash_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct qpnp_flash_led *led = dev_get_drvdata(&pdev->dev);
|
||||
int i = led->num_led_nodes;
|
||||
|
||||
led_classdev_unregister(&led->snode->cdev);
|
||||
while (i > 0)
|
||||
led_classdev_unregister(&led->fnode[--i].cdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct of_device_id qpnp_flash_led_match_table[] = {
|
||||
{ .compatible = "qcom,qpnp-flash-led-v2",},
|
||||
{ },
|
||||
};
|
||||
|
||||
static struct platform_driver qpnp_flash_led_driver = {
|
||||
.driver = {
|
||||
.name = "qcom,qpnp-flash-led-v2",
|
||||
.of_match_table = qpnp_flash_led_match_table,
|
||||
},
|
||||
.probe = qpnp_flash_led_probe,
|
||||
.remove = qpnp_flash_led_remove,
|
||||
};
|
||||
|
||||
static int __init qpnp_flash_led_init(void)
|
||||
{
|
||||
return platform_driver_register(&qpnp_flash_led_driver);
|
||||
}
|
||||
late_initcall(qpnp_flash_led_init);
|
||||
|
||||
static void __exit qpnp_flash_led_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&qpnp_flash_led_driver);
|
||||
}
|
||||
module_exit(qpnp_flash_led_exit);
|
||||
|
||||
MODULE_DESCRIPTION("QPNP Flash LED driver v2");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("leds:leds-qpnp-flash-v2");
|
44
include/linux/leds-qpnp-flash-v2.h
Normal file
44
include/linux/leds-qpnp-flash-v2.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/* Copyright (c) 2016, 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. 1
|
||||
*/
|
||||
|
||||
#ifndef __LEDS_QPNP_FLASH_V2_H
|
||||
#define __LEDS_QPNP_FLASH_V2_H
|
||||
|
||||
#include <linux/leds.h>
|
||||
#include "leds.h"
|
||||
|
||||
/*
|
||||
* Configurations for each individual LED
|
||||
*/
|
||||
struct flash_node_data {
|
||||
struct platform_device *pdev;
|
||||
struct led_classdev cdev;
|
||||
struct pinctrl *pinctrl;
|
||||
struct pinctrl_state *gpio_state_active;
|
||||
struct pinctrl_state *gpio_state_suspend;
|
||||
int ires_ua;
|
||||
u16 prgm_current;
|
||||
u8 duration;
|
||||
u8 id;
|
||||
u8 type;
|
||||
u8 ires;
|
||||
u8 hdrm_val;
|
||||
u8 brightness;
|
||||
bool led_on;
|
||||
};
|
||||
|
||||
struct flash_switch_data {
|
||||
struct platform_device *pdev;
|
||||
struct led_classdev cdev;
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Add table
Reference in a new issue