mirror of
https://github.com/team-infusion-developers/android_kernel_samsung_msm8976.git
synced 2024-10-31 18:09:19 +00:00
leds: pmic8058: Add PMIC leds driver
Add PMIC8058 based keyboard backlight and low current leds driver. keyboard backlight led supports upto 16 levels of backlight intensity, which is mapped to 0..255 levels of brightness of led subsystem. Three low current(40mA) led drivers of PMIC are also supported in this driver. CRs-Fixed: 211772 Change-Id: Ia65b083f6070666aaad95851c9b843332ce35c82 Signed-off-by: Trilok Soni <tsoni@qualcomm.com>
This commit is contained in:
parent
8fb0d4dd79
commit
bee06f8516
4 changed files with 478 additions and 0 deletions
|
@ -399,6 +399,15 @@ config LEDS_MSM_PMIC
|
|||
(Power Management IC) chip on Qualcomm reference boards,
|
||||
for example SURF and FFAs.
|
||||
|
||||
config LEDS_PMIC8058
|
||||
tristate "LED Support for Qualcomm PMIC8058"
|
||||
default n
|
||||
depends on PMIC8058
|
||||
help
|
||||
This option enables support for LEDs connected over PMIC8058
|
||||
(Power Management IC) chip on Qualcomm reference boards,
|
||||
for example SURF and FFAs.
|
||||
|
||||
config LEDS_INTEL_SS4200
|
||||
tristate "LED driver for Intel NAS SS4200 series"
|
||||
depends on LEDS_CLASS
|
||||
|
|
|
@ -56,6 +56,7 @@ obj-$(CONFIG_LEDS_RENESAS_TPU) += leds-renesas-tpu.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_PMIC8058) += leds-pmic8058.o
|
||||
obj-$(CONFIG_LEDS_PMIC_MPP) += leds-pmic-mpp.o
|
||||
obj-$(CONFIG_LEDS_MSM_PDM) += leds-msm-pdm.o
|
||||
|
||||
|
|
428
drivers/leds/leds-pmic8058.c
Normal file
428
drivers/leds/leds-pmic8058.c
Normal file
|
@ -0,0 +1,428 @@
|
|||
/* Copyright (c) 2010, 2011, 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/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/mfd/pm8xxx/core.h>
|
||||
#include <linux/leds-pmic8058.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
#define SSBI_REG_ADDR_DRV_KEYPAD 0x48
|
||||
#define PM8058_DRV_KEYPAD_BL_MASK 0xf0
|
||||
#define PM8058_DRV_KEYPAD_BL_SHIFT 0x04
|
||||
|
||||
#define SSBI_REG_ADDR_FLASH_DRV0 0x49
|
||||
#define PM8058_DRV_FLASH_MASK 0xf0
|
||||
#define PM8058_DRV_FLASH_SHIFT 0x04
|
||||
|
||||
#define SSBI_REG_ADDR_FLASH_DRV1 0xFB
|
||||
|
||||
#define SSBI_REG_ADDR_LED_CTRL_BASE 0x131
|
||||
#define SSBI_REG_ADDR_LED_CTRL(n) (SSBI_REG_ADDR_LED_CTRL_BASE + (n))
|
||||
#define PM8058_DRV_LED_CTRL_MASK 0xf8
|
||||
#define PM8058_DRV_LED_CTRL_SHIFT 0x03
|
||||
|
||||
#define MAX_FLASH_CURRENT 300
|
||||
#define MAX_KEYPAD_CURRENT 300
|
||||
#define MAX_KEYPAD_BL_LEVEL (1 << 4)
|
||||
#define MAX_LED_DRV_LEVEL 20 /* 2 * 20 mA */
|
||||
|
||||
#define PMIC8058_LED_OFFSET(id) ((id) - PMIC8058_ID_LED_0)
|
||||
|
||||
struct pmic8058_led_data {
|
||||
struct device *dev;
|
||||
struct led_classdev cdev;
|
||||
int id;
|
||||
enum led_brightness brightness;
|
||||
u8 flags;
|
||||
struct work_struct work;
|
||||
struct mutex lock;
|
||||
spinlock_t value_lock;
|
||||
u8 reg_kp;
|
||||
u8 reg_led_ctrl[3];
|
||||
u8 reg_flash_led0;
|
||||
u8 reg_flash_led1;
|
||||
};
|
||||
|
||||
#define PM8058_MAX_LEDS 7
|
||||
static struct pmic8058_led_data led_data[PM8058_MAX_LEDS];
|
||||
|
||||
static void kp_bl_set(struct pmic8058_led_data *led, enum led_brightness value)
|
||||
{
|
||||
int rc;
|
||||
u8 level;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&led->value_lock, flags);
|
||||
level = (value << PM8058_DRV_KEYPAD_BL_SHIFT) &
|
||||
PM8058_DRV_KEYPAD_BL_MASK;
|
||||
|
||||
led->reg_kp &= ~PM8058_DRV_KEYPAD_BL_MASK;
|
||||
led->reg_kp |= level;
|
||||
spin_unlock_irqrestore(&led->value_lock, flags);
|
||||
|
||||
rc = pm8xxx_writeb(led->dev->parent, SSBI_REG_ADDR_DRV_KEYPAD,
|
||||
led->reg_kp);
|
||||
if (rc)
|
||||
pr_err("%s: can't set keypad backlight level\n", __func__);
|
||||
}
|
||||
|
||||
static enum led_brightness kp_bl_get(struct pmic8058_led_data *led)
|
||||
{
|
||||
if ((led->reg_kp & PM8058_DRV_KEYPAD_BL_MASK) >>
|
||||
PM8058_DRV_KEYPAD_BL_SHIFT)
|
||||
return LED_FULL;
|
||||
else
|
||||
return LED_OFF;
|
||||
}
|
||||
|
||||
static void led_lc_set(struct pmic8058_led_data *led, enum led_brightness value)
|
||||
{
|
||||
unsigned long flags;
|
||||
int rc, offset;
|
||||
u8 level, tmp;
|
||||
|
||||
spin_lock_irqsave(&led->value_lock, flags);
|
||||
|
||||
level = (led->brightness << PM8058_DRV_LED_CTRL_SHIFT) &
|
||||
PM8058_DRV_LED_CTRL_MASK;
|
||||
|
||||
offset = PMIC8058_LED_OFFSET(led->id);
|
||||
tmp = led->reg_led_ctrl[offset];
|
||||
|
||||
tmp &= ~PM8058_DRV_LED_CTRL_MASK;
|
||||
tmp |= level;
|
||||
spin_unlock_irqrestore(&led->value_lock, flags);
|
||||
|
||||
rc = pm8xxx_writeb(led->dev->parent, SSBI_REG_ADDR_LED_CTRL(offset),
|
||||
tmp);
|
||||
if (rc) {
|
||||
dev_err(led->cdev.dev, "can't set (%d) led value\n",
|
||||
led->id);
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&led->value_lock, flags);
|
||||
led->reg_led_ctrl[offset] = tmp;
|
||||
spin_unlock_irqrestore(&led->value_lock, flags);
|
||||
}
|
||||
|
||||
static enum led_brightness led_lc_get(struct pmic8058_led_data *led)
|
||||
{
|
||||
int offset;
|
||||
u8 value;
|
||||
|
||||
offset = PMIC8058_LED_OFFSET(led->id);
|
||||
value = led->reg_led_ctrl[offset];
|
||||
|
||||
if ((value & PM8058_DRV_LED_CTRL_MASK) >>
|
||||
PM8058_DRV_LED_CTRL_SHIFT)
|
||||
return LED_FULL;
|
||||
else
|
||||
return LED_OFF;
|
||||
}
|
||||
|
||||
static void
|
||||
led_flash_set(struct pmic8058_led_data *led, enum led_brightness value)
|
||||
{
|
||||
int rc;
|
||||
u8 level;
|
||||
unsigned long flags;
|
||||
u8 reg_flash_led;
|
||||
u16 reg_addr;
|
||||
|
||||
spin_lock_irqsave(&led->value_lock, flags);
|
||||
level = (value << PM8058_DRV_FLASH_SHIFT) &
|
||||
PM8058_DRV_FLASH_MASK;
|
||||
|
||||
if (led->id == PMIC8058_ID_FLASH_LED_0) {
|
||||
led->reg_flash_led0 &= ~PM8058_DRV_FLASH_MASK;
|
||||
led->reg_flash_led0 |= level;
|
||||
reg_flash_led = led->reg_flash_led0;
|
||||
reg_addr = SSBI_REG_ADDR_FLASH_DRV0;
|
||||
} else {
|
||||
led->reg_flash_led1 &= ~PM8058_DRV_FLASH_MASK;
|
||||
led->reg_flash_led1 |= level;
|
||||
reg_flash_led = led->reg_flash_led1;
|
||||
reg_addr = SSBI_REG_ADDR_FLASH_DRV1;
|
||||
}
|
||||
spin_unlock_irqrestore(&led->value_lock, flags);
|
||||
|
||||
rc = pm8xxx_writeb(led->dev->parent, reg_addr, reg_flash_led);
|
||||
if (rc)
|
||||
pr_err("%s: can't set flash led%d level %d\n", __func__,
|
||||
led->id, rc);
|
||||
}
|
||||
|
||||
int pm8058_set_flash_led_current(enum pmic8058_leds id, unsigned mA)
|
||||
{
|
||||
struct pmic8058_led_data *led;
|
||||
|
||||
if ((id < PMIC8058_ID_FLASH_LED_0) || (id > PMIC8058_ID_FLASH_LED_1)) {
|
||||
pr_err("%s: invalid LED ID (%d) specified\n", __func__, id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
led = &led_data[id];
|
||||
if (!led) {
|
||||
pr_err("%s: flash led not available\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (mA > MAX_FLASH_CURRENT)
|
||||
return -EINVAL;
|
||||
|
||||
led_flash_set(led, mA / 20);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(pm8058_set_flash_led_current);
|
||||
|
||||
int pm8058_set_led_current(enum pmic8058_leds id, unsigned mA)
|
||||
{
|
||||
struct pmic8058_led_data *led;
|
||||
int brightness = 0;
|
||||
|
||||
if ((id < PMIC8058_ID_LED_KB_LIGHT) || (id > PMIC8058_ID_FLASH_LED_1)) {
|
||||
pr_err("%s: invalid LED ID (%d) specified\n", __func__, id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
led = &led_data[id];
|
||||
if (!led) {
|
||||
pr_err("%s: flash led not available\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (id) {
|
||||
case PMIC8058_ID_LED_0:
|
||||
case PMIC8058_ID_LED_1:
|
||||
case PMIC8058_ID_LED_2:
|
||||
brightness = mA / 2;
|
||||
if (brightness > led->cdev.max_brightness)
|
||||
return -EINVAL;
|
||||
led_lc_set(led, brightness);
|
||||
break;
|
||||
|
||||
case PMIC8058_ID_LED_KB_LIGHT:
|
||||
case PMIC8058_ID_FLASH_LED_0:
|
||||
case PMIC8058_ID_FLASH_LED_1:
|
||||
brightness = mA / 20;
|
||||
if (brightness > led->cdev.max_brightness)
|
||||
return -EINVAL;
|
||||
if (id == PMIC8058_ID_LED_KB_LIGHT)
|
||||
kp_bl_set(led, brightness);
|
||||
else
|
||||
led_flash_set(led, brightness);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(pm8058_set_led_current);
|
||||
|
||||
static void pmic8058_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct pmic8058_led_data *led;
|
||||
unsigned long flags;
|
||||
|
||||
led = container_of(led_cdev, struct pmic8058_led_data, cdev);
|
||||
|
||||
spin_lock_irqsave(&led->value_lock, flags);
|
||||
led->brightness = value;
|
||||
schedule_work(&led->work);
|
||||
spin_unlock_irqrestore(&led->value_lock, flags);
|
||||
}
|
||||
|
||||
static void pmic8058_led_work(struct work_struct *work)
|
||||
{
|
||||
struct pmic8058_led_data *led = container_of(work,
|
||||
struct pmic8058_led_data, work);
|
||||
|
||||
mutex_lock(&led->lock);
|
||||
|
||||
switch (led->id) {
|
||||
case PMIC8058_ID_LED_KB_LIGHT:
|
||||
kp_bl_set(led, led->brightness);
|
||||
break;
|
||||
case PMIC8058_ID_LED_0:
|
||||
case PMIC8058_ID_LED_1:
|
||||
case PMIC8058_ID_LED_2:
|
||||
led_lc_set(led, led->brightness);
|
||||
break;
|
||||
case PMIC8058_ID_FLASH_LED_0:
|
||||
case PMIC8058_ID_FLASH_LED_1:
|
||||
led_flash_set(led, led->brightness);
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&led->lock);
|
||||
}
|
||||
|
||||
static enum led_brightness pmic8058_led_get(struct led_classdev *led_cdev)
|
||||
{
|
||||
struct pmic8058_led_data *led;
|
||||
|
||||
led = container_of(led_cdev, struct pmic8058_led_data, cdev);
|
||||
|
||||
switch (led->id) {
|
||||
case PMIC8058_ID_LED_KB_LIGHT:
|
||||
return kp_bl_get(led);
|
||||
case PMIC8058_ID_LED_0:
|
||||
case PMIC8058_ID_LED_1:
|
||||
case PMIC8058_ID_LED_2:
|
||||
return led_lc_get(led);
|
||||
}
|
||||
return LED_OFF;
|
||||
}
|
||||
|
||||
static int pmic8058_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct pmic8058_leds_platform_data *pdata = pdev->dev.platform_data;
|
||||
struct pmic8058_led_data *led_dat;
|
||||
struct pmic8058_led *curr_led;
|
||||
int rc, i = 0;
|
||||
u8 reg_kp;
|
||||
u8 reg_led_ctrl[3];
|
||||
u8 reg_flash_led0;
|
||||
u8 reg_flash_led1;
|
||||
|
||||
if (pdata == NULL) {
|
||||
dev_err(&pdev->dev, "platform data not supplied\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = pm8xxx_readb(pdev->dev.parent, SSBI_REG_ADDR_DRV_KEYPAD, ®_kp);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "can't get keypad backlight level\n");
|
||||
goto err_reg_read;
|
||||
}
|
||||
|
||||
rc = pm8xxx_read_buf(pdev->dev.parent, SSBI_REG_ADDR_LED_CTRL_BASE,
|
||||
reg_led_ctrl, 3);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "can't get led levels\n");
|
||||
goto err_reg_read;
|
||||
}
|
||||
|
||||
rc = pm8xxx_readb(pdev->dev.parent, SSBI_REG_ADDR_FLASH_DRV0,
|
||||
®_flash_led0);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "can't read flash led0\n");
|
||||
goto err_reg_read;
|
||||
}
|
||||
|
||||
rc = pm8xxx_readb(pdev->dev.parent, SSBI_REG_ADDR_FLASH_DRV1,
|
||||
®_flash_led1);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "can't get flash led1\n");
|
||||
goto err_reg_read;
|
||||
}
|
||||
|
||||
for (i = 0; i < pdata->num_leds; i++) {
|
||||
curr_led = &pdata->leds[i];
|
||||
led_dat = &led_data[curr_led->id];
|
||||
|
||||
led_dat->cdev.name = curr_led->name;
|
||||
led_dat->cdev.default_trigger = curr_led->default_trigger;
|
||||
led_dat->cdev.brightness_set = pmic8058_led_set;
|
||||
led_dat->cdev.brightness_get = pmic8058_led_get;
|
||||
led_dat->cdev.brightness = LED_OFF;
|
||||
led_dat->cdev.max_brightness = curr_led->max_brightness;
|
||||
led_dat->cdev.flags = LED_CORE_SUSPENDRESUME;
|
||||
|
||||
led_dat->id = curr_led->id;
|
||||
led_dat->reg_kp = reg_kp;
|
||||
memcpy(led_data->reg_led_ctrl, reg_led_ctrl,
|
||||
sizeof(reg_led_ctrl));
|
||||
led_dat->reg_flash_led0 = reg_flash_led0;
|
||||
led_dat->reg_flash_led1 = reg_flash_led1;
|
||||
|
||||
if (!((led_dat->id >= PMIC8058_ID_LED_KB_LIGHT) &&
|
||||
(led_dat->id <= PMIC8058_ID_FLASH_LED_1))) {
|
||||
dev_err(&pdev->dev, "invalid LED ID (%d) specified\n",
|
||||
led_dat->id);
|
||||
rc = -EINVAL;
|
||||
goto fail_id_check;
|
||||
}
|
||||
|
||||
led_dat->dev = &pdev->dev;
|
||||
|
||||
mutex_init(&led_dat->lock);
|
||||
spin_lock_init(&led_dat->value_lock);
|
||||
INIT_WORK(&led_dat->work, pmic8058_led_work);
|
||||
|
||||
rc = led_classdev_register(&pdev->dev, &led_dat->cdev);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "unable to register led %d\n",
|
||||
led_dat->id);
|
||||
goto fail_id_check;
|
||||
}
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, led_data);
|
||||
|
||||
return 0;
|
||||
|
||||
err_reg_read:
|
||||
fail_id_check:
|
||||
if (i > 0) {
|
||||
for (i = i - 1; i >= 0; i--)
|
||||
led_classdev_unregister(&led_data[i].cdev);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int pmic8058_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
int i;
|
||||
struct pmic8058_leds_platform_data *pdata = pdev->dev.platform_data;
|
||||
struct pmic8058_led_data *led = platform_get_drvdata(pdev);
|
||||
|
||||
for (i = 0; i < pdata->num_leds; i++) {
|
||||
led_classdev_unregister(&led[led->id].cdev);
|
||||
cancel_work_sync(&led[led->id].work);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver pmic8058_led_driver = {
|
||||
.probe = pmic8058_led_probe,
|
||||
.remove = pmic8058_led_remove,
|
||||
.driver = {
|
||||
.name = "pm8058-led",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init pmic8058_led_init(void)
|
||||
{
|
||||
return platform_driver_register(&pmic8058_led_driver);
|
||||
}
|
||||
module_init(pmic8058_led_init);
|
||||
|
||||
static void __exit pmic8058_led_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&pmic8058_led_driver);
|
||||
}
|
||||
module_exit(pmic8058_led_exit);
|
||||
|
||||
MODULE_DESCRIPTION("PMIC8058 LEDs driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_VERSION("1.0");
|
||||
MODULE_ALIAS("platform:pmic8058-led");
|
40
include/linux/leds-pmic8058.h
Normal file
40
include/linux/leds-pmic8058.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/* Copyright (c) 2010, 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.
|
||||
*
|
||||
*/
|
||||
#ifndef __LEDS_PMIC8058_H__
|
||||
#define __LEDS_PMIC8058_H__
|
||||
|
||||
enum pmic8058_leds {
|
||||
PMIC8058_ID_LED_KB_LIGHT = 1,
|
||||
PMIC8058_ID_LED_0,
|
||||
PMIC8058_ID_LED_1,
|
||||
PMIC8058_ID_LED_2,
|
||||
PMIC8058_ID_FLASH_LED_0,
|
||||
PMIC8058_ID_FLASH_LED_1,
|
||||
};
|
||||
|
||||
struct pmic8058_led {
|
||||
const char *name;
|
||||
const char *default_trigger;
|
||||
unsigned max_brightness;
|
||||
int id;
|
||||
};
|
||||
|
||||
struct pmic8058_leds_platform_data {
|
||||
int num_leds;
|
||||
struct pmic8058_led *leds;
|
||||
};
|
||||
|
||||
int pm8058_set_flash_led_current(enum pmic8058_leds id, unsigned mA);
|
||||
int pm8058_set_led_current(enum pmic8058_leds id, unsigned mA);
|
||||
|
||||
#endif /* __LEDS_PMIC8058_H__ */
|
Loading…
Reference in a new issue