Add samsung specific changes
This commit is contained in:
@@ -118,6 +118,16 @@ config LEDS_AW2013
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called leds-aw2013.
|
||||
|
||||
config LEDS_AW2016
|
||||
tristate "LED Support for AW2016"
|
||||
depends on LEDS_CLASS && I2C
|
||||
help
|
||||
this option enables support for the AW2016 RGB LED connected
|
||||
through I2C. Say Y to enable support for the AW2016 LED.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called leds-aw2016.
|
||||
|
||||
config LEDS_BCM6328
|
||||
tristate "LED Support for Broadcom BCM6328"
|
||||
depends on LEDS_CLASS
|
||||
@@ -277,6 +287,17 @@ config LEDS_NET48XX
|
||||
This option enables support for the Soekris net4801 and net4826 error
|
||||
LED.
|
||||
|
||||
config LEDS_QPNP_FLASH_V2
|
||||
tristate "Support for QPNP V2 Flash LEDs"
|
||||
depends on LEDS_CLASS && MFD_SPMI_PMIC
|
||||
select LEDS_TRIGGERS
|
||||
help
|
||||
This driver supports the flash V2 LED functionality of
|
||||
Qualcomm Technologies, Inc. QPNP PMICs. This driver supports PMICs
|
||||
starting from PMI8998, PM8150L and their derivatives. It can configure
|
||||
the flash LED target current for several independent channels. It also
|
||||
supports various over current and over temperature mitigation features.
|
||||
|
||||
config LEDS_WRAP
|
||||
tristate "LED Support for the WRAP series LEDs"
|
||||
depends on LEDS_CLASS
|
||||
@@ -578,6 +599,21 @@ config LEDS_PWM
|
||||
help
|
||||
This option enables support for pwm driven LEDs
|
||||
|
||||
config LEDS_QTI_FLASH
|
||||
tristate "Support for QTI Flash LEDs"
|
||||
depends on LEDS_CLASS_FLASH
|
||||
depends on MFD_SPMI_PMIC
|
||||
select LEDS_TRIGGERS
|
||||
help
|
||||
This driver supports flash LED peripheral that is present on
|
||||
some Qualcomm Technologies, Inc. PMICs (e.g. PM8350C). It can
|
||||
configure the flash LED target current for several independent
|
||||
channels. It also supports various over current and over
|
||||
temperature mitigation features.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called leds-qti-flash.
|
||||
|
||||
config LEDS_REGULATOR
|
||||
tristate "REGULATOR driven LED support"
|
||||
depends on LEDS_CLASS
|
||||
@@ -892,6 +928,29 @@ config LEDS_ACER_A500
|
||||
This option enables support for the Power Button LED of
|
||||
Acer Iconia Tab A500.
|
||||
|
||||
config LEDS_QTI_TRI_LED
|
||||
tristate "LED support for Qualcomm Technologies, Inc. TRI_LED"
|
||||
depends on LEDS_CLASS && MFD_SPMI_PMIC && PWM && OF
|
||||
help
|
||||
This driver supports the TRI_LED module found in
|
||||
Qualcomm Technologies, Inc. PMIC chips. TRI_LED supports 3
|
||||
LED drivers at max and each is controlled by a PWM channel
|
||||
used for dimming or blinking.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called leds-qti-tri-led.
|
||||
|
||||
config LEDS_QPNP_VIBRATOR_LDO
|
||||
tristate "Vibrator-LDO support for QPNP PMIC"
|
||||
depends on LEDS_CLASS && MFD_SPMI_PMIC
|
||||
help
|
||||
This option enables device driver support for the vibrator-ldo
|
||||
peripheral found on Qualcomm Technologies, Inc. QPNP PMICs.
|
||||
The vibrator-ldo peripheral is capable of driving ERM vibrators.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called leds-qpnp-vibrator-ldo.
|
||||
|
||||
source "drivers/leds/blink/Kconfig"
|
||||
|
||||
comment "Flash and Torch LED drivers"
|
||||
|
@@ -16,6 +16,7 @@ obj-$(CONFIG_LEDS_APU) += leds-apu.o
|
||||
obj-$(CONFIG_LEDS_ARIEL) += leds-ariel.o
|
||||
obj-$(CONFIG_LEDS_AW200XX) += leds-aw200xx.o
|
||||
obj-$(CONFIG_LEDS_AW2013) += leds-aw2013.o
|
||||
obj-$(CONFIG_LEDS_AW2016) += leds-aw2016.o
|
||||
obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o
|
||||
obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o
|
||||
obj-$(CONFIG_LEDS_BD2606MVV) += leds-bd2606mvv.o
|
||||
@@ -76,6 +77,7 @@ obj-$(CONFIG_LEDS_PCA995X) += leds-pca995x.o
|
||||
obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o
|
||||
obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o
|
||||
obj-$(CONFIG_LEDS_PWM) += leds-pwm.o
|
||||
obj-$(CONFIG_LEDS_QTI_FLASH) += leds-qti-flash.o
|
||||
obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o
|
||||
obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o
|
||||
obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o
|
||||
@@ -88,6 +90,9 @@ obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o
|
||||
obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o
|
||||
obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o
|
||||
obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o
|
||||
obj-$(CONFIG_LEDS_QPNP_FLASH_V2) += leds-qpnp-flash-v2.o
|
||||
obj-$(CONFIG_LEDS_QPNP_VIBRATOR_LDO) += leds-qpnp-vibrator-ldo.o
|
||||
obj-$(CONFIG_LEDS_QTI_TRI_LED) += leds-qti-tri-led.o
|
||||
|
||||
# LED SPI Drivers
|
||||
obj-$(CONFIG_LEDS_CR0014114) += leds-cr0014114.o
|
||||
|
670
drivers/leds/leds-aw2016.c
Normal file
670
drivers/leds/leds-aw2016.c
Normal file
@@ -0,0 +1,670 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2017, 2021, The Linux Foundation. All rights reserved.
|
||||
* Copyright (c) 2022, 2024, Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include "leds-aw2016.h"
|
||||
|
||||
#define AW2016_DRIVER_VERSION "V1.0.3"
|
||||
|
||||
/* register address */
|
||||
#define AW2016_REG_RESET 0x00
|
||||
#define AW2016_REG_GCR1 0x01
|
||||
#define AW2016_REG_STATUS 0x02
|
||||
#define AW2016_REG_PATST 0x03
|
||||
#define AW2016_REG_GCR2 0x04
|
||||
#define AW2016_REG_LEDEN 0x30
|
||||
#define AW2016_REG_LCFG1 0x31
|
||||
#define AW2016_REG_LCFG2 0x32
|
||||
#define AW2016_REG_LCFG3 0x33
|
||||
#define AW2016_REG_PWM1 0x34
|
||||
#define AW2016_REG_PWM2 0x35
|
||||
#define AW2016_REG_PWM3 0x36
|
||||
#define AW2016_REG_LED1T0 0x37
|
||||
#define AW2016_REG_LED1T1 0x38
|
||||
#define AW2016_REG_LED1T2 0x39
|
||||
#define AW2016_REG_LED2T0 0x3A
|
||||
#define AW2016_REG_LED2T1 0x3B
|
||||
#define AW2016_REG_LED2T2 0x3C
|
||||
#define AW2016_REG_LED3T0 0x3D
|
||||
#define AW2016_REG_LED3T1 0x3E
|
||||
#define AW2016_REG_LED3T2 0x3F
|
||||
|
||||
/* register bits */
|
||||
#define AW2016_CHIPID 0x09
|
||||
#define AW2016_CHIP_RESET_MASK 0x55
|
||||
#define AW2016_CHIP_DISABLE_MASK 0x00
|
||||
#define AW2016_CHIP_ENABLE_MASK 0x01
|
||||
#define AW2016_CHARGE_DISABLE_MASK 0x02
|
||||
#define AW2016_LED_BREATH_MODE_MASK 0x10
|
||||
#define AW2016_LED_MANUAL_MODE_MASK 0x00
|
||||
#define AW2016_LED_BREATHE_PWM_MASK 0xFF
|
||||
#define AW2016_LED_MANUAL_PWM_MASK 0xFF
|
||||
#define AW2016_LED_FADEIN_MODE_MASK 0x20
|
||||
#define AW2016_LED_FADEOUT_MODE_MASK 0x40
|
||||
#define AW2016_CHIP_STANDBY 0x02
|
||||
|
||||
#define MAX_RISE_TIME_MS 15
|
||||
#define MAX_HOLD_TIME_MS 15
|
||||
#define MAX_FALL_TIME_MS 15
|
||||
#define MAX_OFF_TIME_MS 15
|
||||
|
||||
/* aw2016 register read/write access*/
|
||||
#define REG_NONE_ACCESS 0
|
||||
#define REG_RD_ACCESS (1 << 0)
|
||||
#define REG_WR_ACCESS (1 << 1)
|
||||
#define AW2016_REG_MAX 0x7F
|
||||
|
||||
const unsigned char aw2016_reg_access[AW2016_REG_MAX] = {
|
||||
[AW2016_REG_RESET] = REG_RD_ACCESS | REG_WR_ACCESS,
|
||||
[AW2016_REG_GCR1] = REG_RD_ACCESS | REG_WR_ACCESS,
|
||||
[AW2016_REG_STATUS] = REG_RD_ACCESS,
|
||||
[AW2016_REG_PATST] = REG_RD_ACCESS,
|
||||
[AW2016_REG_GCR2] = REG_RD_ACCESS | REG_WR_ACCESS,
|
||||
[AW2016_REG_LEDEN] = REG_RD_ACCESS | REG_WR_ACCESS,
|
||||
[AW2016_REG_LCFG1] = REG_RD_ACCESS | REG_WR_ACCESS,
|
||||
[AW2016_REG_LCFG2] = REG_RD_ACCESS | REG_WR_ACCESS,
|
||||
[AW2016_REG_LCFG3] = REG_RD_ACCESS | REG_WR_ACCESS,
|
||||
[AW2016_REG_PWM1] = REG_RD_ACCESS | REG_WR_ACCESS,
|
||||
[AW2016_REG_PWM2] = REG_RD_ACCESS | REG_WR_ACCESS,
|
||||
[AW2016_REG_PWM3] = REG_RD_ACCESS | REG_WR_ACCESS,
|
||||
[AW2016_REG_LED1T0] = REG_RD_ACCESS | REG_WR_ACCESS,
|
||||
[AW2016_REG_LED1T1] = REG_RD_ACCESS | REG_WR_ACCESS,
|
||||
[AW2016_REG_LED1T2] = REG_RD_ACCESS | REG_WR_ACCESS,
|
||||
[AW2016_REG_LED2T0] = REG_RD_ACCESS | REG_WR_ACCESS,
|
||||
[AW2016_REG_LED2T1] = REG_RD_ACCESS | REG_WR_ACCESS,
|
||||
[AW2016_REG_LED2T2] = REG_RD_ACCESS | REG_WR_ACCESS,
|
||||
[AW2016_REG_LED3T0] = REG_RD_ACCESS | REG_WR_ACCESS,
|
||||
[AW2016_REG_LED3T1] = REG_RD_ACCESS | REG_WR_ACCESS,
|
||||
[AW2016_REG_LED3T2] = REG_RD_ACCESS | REG_WR_ACCESS,
|
||||
};
|
||||
|
||||
struct aw2016_led {
|
||||
struct i2c_client *client;
|
||||
struct led_classdev cdev;
|
||||
struct aw2016_platform_data *pdata;
|
||||
struct work_struct brightness_work;
|
||||
struct work_struct blink_work;
|
||||
struct mutex lock;
|
||||
int num_leds;
|
||||
int id;
|
||||
int blinking;
|
||||
};
|
||||
|
||||
static int aw2016_write(struct aw2016_led *led, u8 reg, u8 val)
|
||||
{
|
||||
int ret = -EINVAL, retry_times = 0;
|
||||
|
||||
do {
|
||||
ret = i2c_smbus_write_byte_data(led->client, reg, val);
|
||||
retry_times++;
|
||||
if (retry_times == 5)
|
||||
break;
|
||||
} while (ret < 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int aw2016_read(struct aw2016_led *led, u8 reg, u8 *val)
|
||||
{
|
||||
int ret = -EINVAL, retry_times = 0;
|
||||
|
||||
do {
|
||||
ret = i2c_smbus_read_byte_data(led->client, reg);
|
||||
retry_times++;
|
||||
if (retry_times == 5)
|
||||
break;
|
||||
} while (ret < 0);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void aw2016_soft_reset(struct aw2016_led *led)
|
||||
{
|
||||
aw2016_write(led, AW2016_REG_RESET, AW2016_CHIP_RESET_MASK);
|
||||
usleep_range(5000, 6000);
|
||||
}
|
||||
|
||||
static void aw2016_brightness_work(struct work_struct *work)
|
||||
{
|
||||
struct aw2016_led *led =
|
||||
container_of(work, struct aw2016_led, brightness_work);
|
||||
u8 val;
|
||||
|
||||
mutex_lock(&led->pdata->led->lock);
|
||||
|
||||
/* enable aw2016 if disabled */
|
||||
aw2016_read(led, AW2016_REG_GCR1, &val);
|
||||
if (!(val & AW2016_CHIP_ENABLE_MASK)) {
|
||||
aw2016_write(led, AW2016_REG_GCR1,
|
||||
AW2016_CHARGE_DISABLE_MASK |
|
||||
AW2016_CHIP_ENABLE_MASK);
|
||||
usleep_range(2000, 3000);
|
||||
}
|
||||
|
||||
if (led->cdev.brightness > 0) {
|
||||
if (led->cdev.brightness > led->cdev.max_brightness)
|
||||
led->cdev.brightness = led->cdev.max_brightness;
|
||||
aw2016_write(led, AW2016_REG_GCR2, led->pdata->imax);
|
||||
aw2016_write(led, AW2016_REG_LCFG1 + led->id,
|
||||
(AW2016_LED_MANUAL_MODE_MASK |
|
||||
led->pdata->led_current));
|
||||
aw2016_write(led, AW2016_REG_PWM1 + led->id,
|
||||
led->cdev.brightness);
|
||||
aw2016_read(led, AW2016_REG_LEDEN, &val);
|
||||
aw2016_write(led, AW2016_REG_LEDEN, val | (1 << led->id));
|
||||
} else {
|
||||
aw2016_read(led, AW2016_REG_LEDEN, &val);
|
||||
aw2016_write(led, AW2016_REG_LEDEN, val & (~(1 << led->id)));
|
||||
}
|
||||
|
||||
/*
|
||||
* If value in AW2016_REG_LEDEN is 0, it means the RGB leds are
|
||||
* all off. So we need to power it off.
|
||||
*/
|
||||
aw2016_read(led, AW2016_REG_LEDEN, &val);
|
||||
if (val == 0) {
|
||||
aw2016_write(led, AW2016_REG_GCR1,
|
||||
AW2016_CHARGE_DISABLE_MASK |
|
||||
AW2016_CHIP_DISABLE_MASK);
|
||||
mutex_unlock(&led->pdata->led->lock);
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_unlock(&led->pdata->led->lock);
|
||||
}
|
||||
|
||||
static void aw2016_blink_work(struct work_struct *work)
|
||||
{
|
||||
struct aw2016_led *led =
|
||||
container_of(work, struct aw2016_led, blink_work);
|
||||
u8 val;
|
||||
|
||||
mutex_lock(&led->pdata->led->lock);
|
||||
|
||||
/* enable aw2016 if disabled */
|
||||
aw2016_read(led, AW2016_REG_GCR1, &val);
|
||||
if (!(val & AW2016_CHIP_ENABLE_MASK)) {
|
||||
aw2016_write(led, AW2016_REG_GCR1,
|
||||
AW2016_CHARGE_DISABLE_MASK |
|
||||
AW2016_CHIP_ENABLE_MASK);
|
||||
usleep_range(2000, 3000);
|
||||
}
|
||||
|
||||
led->cdev.brightness = led->blinking ? led->cdev.max_brightness : 0;
|
||||
|
||||
if (led->blinking > 0) {
|
||||
aw2016_write(led, AW2016_REG_GCR2, led->pdata->imax);
|
||||
aw2016_write(led, AW2016_REG_PWM1 + led->id,
|
||||
led->cdev.brightness);
|
||||
aw2016_write(led, AW2016_REG_LED1T0 + led->id * 3,
|
||||
(led->pdata->rise_time_ms << 4 |
|
||||
led->pdata->hold_time_ms));
|
||||
aw2016_write(led, AW2016_REG_LED1T1 + led->id * 3,
|
||||
(led->pdata->fall_time_ms << 4 |
|
||||
led->pdata->off_time_ms));
|
||||
aw2016_write(led, AW2016_REG_LCFG1 + led->id,
|
||||
(AW2016_LED_BREATH_MODE_MASK |
|
||||
led->pdata->led_current));
|
||||
aw2016_read(led, AW2016_REG_LEDEN, &val);
|
||||
aw2016_write(led, AW2016_REG_LEDEN, val | (1 << led->id));
|
||||
} else {
|
||||
aw2016_read(led, AW2016_REG_LEDEN, &val);
|
||||
aw2016_write(led, AW2016_REG_LEDEN, val & (~(1 << led->id)));
|
||||
}
|
||||
|
||||
/*
|
||||
* If value in AW2016_REG_LEDEN is 0, it means the RGB leds are
|
||||
* all off. So we need to power it off.
|
||||
*/
|
||||
aw2016_read(led, AW2016_REG_LEDEN, &val);
|
||||
if (val == 0) {
|
||||
aw2016_write(led, AW2016_REG_GCR1,
|
||||
AW2016_CHARGE_DISABLE_MASK |
|
||||
AW2016_CHIP_DISABLE_MASK);
|
||||
}
|
||||
|
||||
mutex_unlock(&led->pdata->led->lock);
|
||||
}
|
||||
|
||||
static enum led_brightness aw2016_get_brightness(struct led_classdev *cdev)
|
||||
{
|
||||
return cdev->brightness;
|
||||
}
|
||||
|
||||
static void aw2016_set_brightness(struct led_classdev *cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct aw2016_led *led = container_of(cdev, struct aw2016_led, cdev);
|
||||
|
||||
led->cdev.brightness = brightness;
|
||||
|
||||
schedule_work(&led->brightness_work);
|
||||
}
|
||||
|
||||
static ssize_t breath_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct aw2016_led *led = container_of(led_cdev, struct aw2016_led, cdev);
|
||||
|
||||
return led->blinking;
|
||||
}
|
||||
|
||||
static ssize_t breath_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
unsigned long blinking;
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct aw2016_led *led =
|
||||
container_of(led_cdev, struct aw2016_led, cdev);
|
||||
ssize_t ret = -EINVAL;
|
||||
|
||||
ret = kstrtoul(buf, 10, &blinking);
|
||||
if (ret)
|
||||
return ret;
|
||||
led->blinking = (int)blinking;
|
||||
schedule_work(&led->blink_work);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t led_time_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct aw2016_led *led =
|
||||
container_of(led_cdev, struct aw2016_led, cdev);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%d %d %d %d\n",
|
||||
led->pdata->rise_time_ms, led->pdata->hold_time_ms,
|
||||
led->pdata->fall_time_ms, led->pdata->off_time_ms);
|
||||
}
|
||||
|
||||
static ssize_t led_time_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct aw2016_led *led =
|
||||
container_of(led_cdev, struct aw2016_led, cdev);
|
||||
int rc, rise_time_ms, hold_time_ms, fall_time_ms, off_time_ms;
|
||||
|
||||
rc = sscanf(buf, "%d %d %d %d", &rise_time_ms, &hold_time_ms,
|
||||
&fall_time_ms, &off_time_ms);
|
||||
|
||||
mutex_lock(&led->pdata->led->lock);
|
||||
led->pdata->rise_time_ms = (rise_time_ms > MAX_RISE_TIME_MS) ?
|
||||
MAX_RISE_TIME_MS :
|
||||
rise_time_ms;
|
||||
led->pdata->hold_time_ms = (hold_time_ms > MAX_HOLD_TIME_MS) ?
|
||||
MAX_HOLD_TIME_MS :
|
||||
hold_time_ms;
|
||||
led->pdata->fall_time_ms = (fall_time_ms > MAX_FALL_TIME_MS) ?
|
||||
MAX_FALL_TIME_MS :
|
||||
fall_time_ms;
|
||||
led->pdata->off_time_ms =
|
||||
(off_time_ms > MAX_OFF_TIME_MS) ? MAX_OFF_TIME_MS : off_time_ms;
|
||||
led->blinking = 1;
|
||||
mutex_unlock(&led->pdata->led->lock);
|
||||
|
||||
schedule_work(&led->blink_work);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t reg_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct aw2016_led *led =
|
||||
container_of(led_cdev, struct aw2016_led, cdev);
|
||||
|
||||
unsigned char i, reg_val;
|
||||
ssize_t len = 0;
|
||||
|
||||
for (i = 0; i < AW2016_REG_MAX; i++) {
|
||||
if (!(aw2016_reg_access[i] & REG_RD_ACCESS))
|
||||
continue;
|
||||
aw2016_read(led, i, ®_val);
|
||||
len += scnprintf(buf + len, PAGE_SIZE - len,
|
||||
"reg:0x%02x=0x%02x\n", i, reg_val);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t reg_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf,
|
||||
size_t len)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct aw2016_led *led =
|
||||
container_of(led_cdev, struct aw2016_led, cdev);
|
||||
|
||||
unsigned int databuf[2];
|
||||
|
||||
if (sscanf(buf, "%x %x", &databuf[0], &databuf[1]) == 2) {
|
||||
aw2016_write(led, (unsigned char)databuf[0],
|
||||
(unsigned char)databuf[1]);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RW(breath);
|
||||
static DEVICE_ATTR_RW(led_time);
|
||||
static DEVICE_ATTR_RW(reg);
|
||||
|
||||
static struct attribute *aw2016_led_attributes[] = {
|
||||
&dev_attr_breath.attr,
|
||||
&dev_attr_led_time.attr,
|
||||
&dev_attr_reg.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group aw2016_led_attr_group = {
|
||||
.attrs = aw2016_led_attributes
|
||||
};
|
||||
static int aw2016_check_chipid(struct aw2016_led *led)
|
||||
{
|
||||
u8 val;
|
||||
u8 cnt;
|
||||
|
||||
for (cnt = 5; cnt > 0; cnt--) {
|
||||
aw2016_read(led, AW2016_REG_RESET, &val);
|
||||
dev_notice(&led->client->dev, "aw2016 chip id %0x\n", val);
|
||||
if (val == AW2016_CHIPID)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int aw2016_led_err_handle(struct aw2016_led *led_array, int parsed_leds)
|
||||
{
|
||||
int i;
|
||||
/*
|
||||
* If probe fails, cannot free resource of all LEDs, only free
|
||||
* resources of LEDs which have allocated these resource really.
|
||||
*/
|
||||
for (i = 0; i < parsed_leds; i++) {
|
||||
sysfs_remove_group(&led_array[i].cdev.dev->kobj,
|
||||
&aw2016_led_attr_group);
|
||||
led_classdev_unregister(&led_array[i].cdev);
|
||||
cancel_work_sync(&led_array[i].brightness_work);
|
||||
cancel_work_sync(&led_array[i].blink_work);
|
||||
led_array[i].pdata = NULL;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static int aw2016_led_parse_child_node(struct aw2016_led *led_array,
|
||||
struct device_node *node)
|
||||
{
|
||||
struct aw2016_led *led;
|
||||
struct device_node *temp;
|
||||
struct aw2016_platform_data *pdata;
|
||||
int rc = 0, parsed_leds = 0;
|
||||
|
||||
for_each_child_of_node(node, temp) {
|
||||
led = &led_array[parsed_leds];
|
||||
led->client = led_array->client;
|
||||
|
||||
pdata = devm_kzalloc(&led->client->dev,
|
||||
sizeof(struct aw2016_platform_data),
|
||||
GFP_KERNEL);
|
||||
if (!pdata) {
|
||||
dev_err(&led->client->dev,
|
||||
"Failed to allocate memory\n");
|
||||
goto free_err;
|
||||
}
|
||||
pdata->led = led_array;
|
||||
led->pdata = pdata;
|
||||
|
||||
rc = of_property_read_string(temp, "awinic,name",
|
||||
&led->cdev.name);
|
||||
if (rc < 0) {
|
||||
dev_err(&led->client->dev,
|
||||
"Failure reading led name, rc = %d\n", rc);
|
||||
goto free_pdata;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32(temp, "awinic,id", &led->id);
|
||||
if (rc < 0) {
|
||||
dev_err(&led->client->dev,
|
||||
"Failure reading id, rc = %d\n", rc);
|
||||
goto free_pdata;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32(temp, "awinic,imax",
|
||||
&led->pdata->imax);
|
||||
if (rc < 0) {
|
||||
dev_err(&led->client->dev,
|
||||
"Failure reading imax, rc = %d\n", rc);
|
||||
goto free_pdata;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32(temp, "awinic,led-current",
|
||||
&led->pdata->led_current);
|
||||
if (rc < 0) {
|
||||
dev_err(&led->client->dev,
|
||||
"Failure reading led-current, rc = %d\n", rc);
|
||||
goto free_pdata;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32(temp, "awinic,max-brightness",
|
||||
&led->cdev.max_brightness);
|
||||
if (rc < 0) {
|
||||
dev_err(&led->client->dev,
|
||||
"Failure reading max-brightness, rc = %d\n",
|
||||
rc);
|
||||
goto free_pdata;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32(temp, "awinic,rise-time-ms",
|
||||
&led->pdata->rise_time_ms);
|
||||
if (rc < 0) {
|
||||
dev_err(&led->client->dev,
|
||||
"Failure reading rise-time-ms, rc = %d\n", rc);
|
||||
goto free_pdata;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32(temp, "awinic,hold-time-ms",
|
||||
&led->pdata->hold_time_ms);
|
||||
if (rc < 0) {
|
||||
dev_err(&led->client->dev,
|
||||
"Failure reading hold-time-ms, rc = %d\n", rc);
|
||||
goto free_pdata;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32(temp, "awinic,fall-time-ms",
|
||||
&led->pdata->fall_time_ms);
|
||||
if (rc < 0) {
|
||||
dev_err(&led->client->dev,
|
||||
"Failure reading fall-time-ms, rc = %d\n", rc);
|
||||
goto free_pdata;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32(temp, "awinic,off-time-ms",
|
||||
&led->pdata->off_time_ms);
|
||||
if (rc < 0) {
|
||||
dev_err(&led->client->dev,
|
||||
"Failure reading off-time-ms, rc = %d\n", rc);
|
||||
goto free_pdata;
|
||||
}
|
||||
|
||||
INIT_WORK(&led->brightness_work, aw2016_brightness_work);
|
||||
INIT_WORK(&led->blink_work, aw2016_blink_work);
|
||||
|
||||
led->cdev.brightness_set = aw2016_set_brightness;
|
||||
led->cdev.brightness_get = aw2016_get_brightness;
|
||||
|
||||
rc = led_classdev_register(&led->client->dev, &led->cdev);
|
||||
if (rc) {
|
||||
dev_err(&led->client->dev,
|
||||
"unable to register led %d,rc=%d\n", led->id,
|
||||
rc);
|
||||
goto free_pdata;
|
||||
}
|
||||
|
||||
rc = sysfs_create_group(&led->cdev.dev->kobj,
|
||||
&aw2016_led_attr_group);
|
||||
if (rc) {
|
||||
dev_err(&led->client->dev, "led sysfs rc: %d\n", rc);
|
||||
goto free_class;
|
||||
}
|
||||
parsed_leds++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
free_class:
|
||||
aw2016_led_err_handle(led_array, parsed_leds);
|
||||
led_classdev_unregister(&led_array[parsed_leds].cdev);
|
||||
cancel_work_sync(&led_array[parsed_leds].brightness_work);
|
||||
cancel_work_sync(&led_array[parsed_leds].blink_work);
|
||||
led_array[parsed_leds].pdata = NULL;
|
||||
return rc;
|
||||
|
||||
free_pdata:
|
||||
aw2016_led_err_handle(led_array, parsed_leds);
|
||||
return rc;
|
||||
|
||||
free_err:
|
||||
aw2016_led_err_handle(led_array, parsed_leds);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int aw2016_led_probe(struct i2c_client *client)
|
||||
{
|
||||
struct aw2016_led *led_array;
|
||||
struct device_node *node;
|
||||
int ret = -EINVAL, num_leds = 0;
|
||||
|
||||
node = client->dev.of_node;
|
||||
if (node == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
num_leds = of_get_child_count(node);
|
||||
|
||||
if (!num_leds)
|
||||
return -EINVAL;
|
||||
|
||||
led_array = devm_kzalloc(&client->dev,
|
||||
(sizeof(struct aw2016_led) * num_leds),
|
||||
GFP_KERNEL);
|
||||
if (!led_array)
|
||||
return -ENOMEM;
|
||||
|
||||
led_array->client = client;
|
||||
led_array->num_leds = num_leds;
|
||||
|
||||
mutex_init(&led_array->lock);
|
||||
|
||||
ret = aw2016_led_parse_child_node(led_array, node);
|
||||
if (ret) {
|
||||
dev_err(&client->dev, "parsed node error\n");
|
||||
goto free_led_arry;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, led_array);
|
||||
|
||||
ret = aw2016_check_chipid(led_array);
|
||||
if (ret) {
|
||||
dev_err(&client->dev, "Check chip id error\n");
|
||||
goto fail_parsed_node;
|
||||
}
|
||||
|
||||
/* soft rst */
|
||||
aw2016_soft_reset(led_array);
|
||||
|
||||
return 0;
|
||||
|
||||
fail_parsed_node:
|
||||
aw2016_led_err_handle(led_array, num_leds);
|
||||
free_led_arry:
|
||||
mutex_destroy(&led_array->lock);
|
||||
led_array = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void aw2016_led_remove(struct i2c_client *client)
|
||||
{
|
||||
struct aw2016_led *led_array = i2c_get_clientdata(client);
|
||||
int i, parsed_leds = led_array->num_leds;
|
||||
|
||||
for (i = 0; i < parsed_leds; i++) {
|
||||
sysfs_remove_group(&led_array[i].cdev.dev->kobj,
|
||||
&aw2016_led_attr_group);
|
||||
led_classdev_unregister(&led_array[i].cdev);
|
||||
cancel_work_sync(&led_array[i].brightness_work);
|
||||
cancel_work_sync(&led_array[i].blink_work);
|
||||
led_array[i].pdata = NULL;
|
||||
}
|
||||
mutex_destroy(&led_array->lock);
|
||||
led_array = NULL;
|
||||
}
|
||||
|
||||
static void aw2016_led_shutdown(struct i2c_client *client)
|
||||
{
|
||||
struct aw2016_led *led = i2c_get_clientdata(client);
|
||||
|
||||
aw2016_write(led, AW2016_REG_GCR1, AW2016_CHIP_STANDBY);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id aw2016_led_id[] = {
|
||||
{ "aw2016_led", 0 },
|
||||
{},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, aw2016_led_id);
|
||||
|
||||
static const struct of_device_id aw2016_match_table[] = {
|
||||
{
|
||||
.compatible = "awinic,aw2016_led",
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
static struct i2c_driver aw2016_led_driver = {
|
||||
.probe = aw2016_led_probe,
|
||||
.remove = aw2016_led_remove,
|
||||
.shutdown = aw2016_led_shutdown,
|
||||
.driver = {
|
||||
.name = "aw2016_led",
|
||||
.of_match_table = of_match_ptr(aw2016_match_table),
|
||||
},
|
||||
.id_table = aw2016_led_id,
|
||||
};
|
||||
|
||||
static int __init aw2016_led_init(void)
|
||||
{
|
||||
pr_info("%s: driver version: %s\n", __func__, AW2016_DRIVER_VERSION);
|
||||
return i2c_add_driver(&aw2016_led_driver);
|
||||
}
|
||||
module_init(aw2016_led_init);
|
||||
|
||||
static void __exit aw2016_led_exit(void)
|
||||
{
|
||||
i2c_del_driver(&aw2016_led_driver);
|
||||
}
|
||||
module_exit(aw2016_led_exit);
|
||||
|
||||
MODULE_AUTHOR("<liweilei@awinic.com.cn>");
|
||||
MODULE_DESCRIPTION("AWINIC AW2016 LED driver");
|
||||
MODULE_LICENSE("GPL");
|
31
drivers/leds/leds-aw2016.h
Normal file
31
drivers/leds/leds-aw2016.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
|
||||
* Copyright (c) 2022, 2024, Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_AW2016_LED_H__
|
||||
#define __LINUX_AW2016_LED_H__
|
||||
|
||||
/* The definition of each time described as shown in figure.
|
||||
* /-----------\
|
||||
* / | \
|
||||
* /| | |\
|
||||
* / | | | \-----------
|
||||
* |hold_time_ms | |
|
||||
* | | |
|
||||
* rise_time_ms fall_time_ms |
|
||||
* off_time_ms
|
||||
*/
|
||||
|
||||
struct aw2016_platform_data {
|
||||
int imax;
|
||||
int led_current;
|
||||
int rise_time_ms;
|
||||
int hold_time_ms;
|
||||
int fall_time_ms;
|
||||
int off_time_ms;
|
||||
struct aw2016_led *led;
|
||||
};
|
||||
|
||||
#endif
|
3334
drivers/leds/leds-qpnp-flash-v2.c
Normal file
3334
drivers/leds/leds-qpnp-flash-v2.c
Normal file
File diff suppressed because it is too large
Load Diff
431
drivers/leds/leds-qpnp-vibrator-ldo.c
Normal file
431
drivers/leds/leds-qpnp-vibrator-ldo.c
Normal file
@@ -0,0 +1,431 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
|
||||
* Copyright (c) 2022, 2024, Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "%s: " fmt, __func__
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
/* Vibrator-LDO register definitions */
|
||||
#define QPNP_VIB_LDO_REG_STATUS1 0x08
|
||||
#define QPNP_VIB_LDO_VREG_READY BIT(7)
|
||||
|
||||
#define QPNP_VIB_LDO_REG_VSET_LB 0x40
|
||||
|
||||
#define QPNP_VIB_LDO_REG_EN_CTL 0x46
|
||||
#define QPNP_VIB_LDO_EN BIT(7)
|
||||
|
||||
/* Vibrator-LDO voltage settings */
|
||||
#define QPNP_VIB_LDO_VMIN_UV 1504000
|
||||
#define QPNP_VIB_LDO_VMAX_UV 3544000
|
||||
#define QPNP_VIB_LDO_VOLT_STEP_UV 8000
|
||||
|
||||
/*
|
||||
* Define vibration periods: default(5sec), min(50ms), max(15sec) and
|
||||
* overdrive(30ms).
|
||||
*/
|
||||
#define QPNP_VIB_MIN_PLAY_MS 50
|
||||
#define QPNP_VIB_PLAY_MS 5000
|
||||
#define QPNP_VIB_MAX_PLAY_MS 15000
|
||||
#define QPNP_VIB_OVERDRIVE_PLAY_MS 30
|
||||
|
||||
struct vib_ldo_chip {
|
||||
struct led_classdev cdev;
|
||||
struct regmap *regmap;
|
||||
struct mutex lock;
|
||||
struct hrtimer overdrive_timer;
|
||||
struct work_struct overdrive_work;
|
||||
u16 base;
|
||||
int vmax_uV;
|
||||
int overdrive_volt_uV;
|
||||
int ldo_uV;
|
||||
int state;
|
||||
u64 vib_play_ms;
|
||||
bool vib_enabled;
|
||||
bool disable_overdrive;
|
||||
};
|
||||
|
||||
static inline int qpnp_vib_ldo_poll_status(struct vib_ldo_chip *chip)
|
||||
{
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read_poll_timeout(chip->regmap,
|
||||
chip->base + QPNP_VIB_LDO_REG_STATUS1, val,
|
||||
val & QPNP_VIB_LDO_VREG_READY, 100, 1000);
|
||||
if (ret < 0) {
|
||||
pr_err("Vibrator LDO vreg_ready timeout, status=0x%02x, ret=%d\n",
|
||||
val, ret);
|
||||
|
||||
/* Keep VIB_LDO disabled */
|
||||
regmap_update_bits(chip->regmap,
|
||||
chip->base + QPNP_VIB_LDO_REG_EN_CTL,
|
||||
QPNP_VIB_LDO_EN, 0);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qpnp_vib_ldo_set_voltage(struct vib_ldo_chip *chip, int new_uV)
|
||||
{
|
||||
u32 vlevel;
|
||||
u8 reg[2];
|
||||
int ret;
|
||||
|
||||
if (chip->ldo_uV == new_uV)
|
||||
return 0;
|
||||
|
||||
vlevel = roundup(new_uV, QPNP_VIB_LDO_VOLT_STEP_UV) / 1000;
|
||||
reg[0] = vlevel & 0xff;
|
||||
reg[1] = (vlevel & 0xff00) >> 8;
|
||||
ret = regmap_bulk_write(chip->regmap,
|
||||
chip->base + QPNP_VIB_LDO_REG_VSET_LB, reg, 2);
|
||||
if (ret < 0) {
|
||||
pr_err("regmap write failed, ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (chip->vib_enabled) {
|
||||
ret = qpnp_vib_ldo_poll_status(chip);
|
||||
if (ret < 0) {
|
||||
pr_err("Vibrator LDO status polling timedout\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
chip->ldo_uV = new_uV;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int qpnp_vib_ldo_enable(struct vib_ldo_chip *chip, bool enable)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (chip->vib_enabled == enable)
|
||||
return 0;
|
||||
|
||||
ret = regmap_update_bits(chip->regmap,
|
||||
chip->base + QPNP_VIB_LDO_REG_EN_CTL,
|
||||
QPNP_VIB_LDO_EN,
|
||||
enable ? QPNP_VIB_LDO_EN : 0);
|
||||
if (ret < 0) {
|
||||
pr_err("Program Vibrator LDO %s is failed, ret=%d\n",
|
||||
enable ? "enable" : "disable", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
ret = qpnp_vib_ldo_poll_status(chip);
|
||||
if (ret < 0) {
|
||||
pr_err("Vibrator LDO status polling timedout\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
chip->vib_enabled = enable;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qpnp_vibrator_play_on(struct vib_ldo_chip *chip)
|
||||
{
|
||||
int volt_uV;
|
||||
int ret;
|
||||
|
||||
volt_uV = chip->vmax_uV;
|
||||
if (!chip->disable_overdrive)
|
||||
volt_uV = chip->overdrive_volt_uV ? chip->overdrive_volt_uV
|
||||
: min(chip->vmax_uV * 2, QPNP_VIB_LDO_VMAX_UV);
|
||||
|
||||
ret = qpnp_vib_ldo_set_voltage(chip, volt_uV);
|
||||
if (ret < 0) {
|
||||
pr_err("set voltage = %duV failed, ret=%d\n", volt_uV, ret);
|
||||
return ret;
|
||||
}
|
||||
pr_debug("voltage set to %d uV\n", volt_uV);
|
||||
|
||||
ret = qpnp_vib_ldo_enable(chip, true);
|
||||
if (ret < 0) {
|
||||
pr_err("vibration enable failed, ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!chip->disable_overdrive)
|
||||
hrtimer_start(&chip->overdrive_timer,
|
||||
ms_to_ktime(QPNP_VIB_OVERDRIVE_PLAY_MS),
|
||||
HRTIMER_MODE_REL);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void qpnp_vib_overdrive_work(struct work_struct *work)
|
||||
{
|
||||
struct vib_ldo_chip *chip = container_of(work, struct vib_ldo_chip,
|
||||
overdrive_work);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
|
||||
/* LDO voltage update not required if Vibration disabled */
|
||||
if (!chip->vib_enabled)
|
||||
goto unlock;
|
||||
|
||||
ret = qpnp_vib_ldo_set_voltage(chip, chip->vmax_uV);
|
||||
if (ret < 0) {
|
||||
pr_err("set vibration voltage = %duV failed, ret=%d\n",
|
||||
chip->vmax_uV, ret);
|
||||
qpnp_vib_ldo_enable(chip, false);
|
||||
goto unlock;
|
||||
}
|
||||
pr_debug("voltage set to %d\n", chip->vmax_uV);
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&chip->lock);
|
||||
}
|
||||
|
||||
static enum hrtimer_restart vib_overdrive_timer(struct hrtimer *timer)
|
||||
{
|
||||
struct vib_ldo_chip *chip = container_of(timer, struct vib_ldo_chip,
|
||||
overdrive_timer);
|
||||
schedule_work(&chip->overdrive_work);
|
||||
pr_debug("overdrive timer expired\n");
|
||||
return HRTIMER_NORESTART;
|
||||
}
|
||||
|
||||
static ssize_t qpnp_vib_show_vmax(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *cdev = dev_get_drvdata(dev);
|
||||
struct vib_ldo_chip *chip = container_of(cdev, struct vib_ldo_chip,
|
||||
cdev);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n", chip->vmax_uV / 1000);
|
||||
}
|
||||
|
||||
static ssize_t qpnp_vib_store_vmax(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct led_classdev *cdev = dev_get_drvdata(dev);
|
||||
struct vib_ldo_chip *chip = container_of(cdev, struct vib_ldo_chip,
|
||||
cdev);
|
||||
int data, ret;
|
||||
|
||||
ret = kstrtoint(buf, 10, &data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data = data * 1000; /* Convert to microvolts */
|
||||
|
||||
/* check against vibrator ldo min/max voltage limits */
|
||||
data = min(data, QPNP_VIB_LDO_VMAX_UV);
|
||||
data = max(data, QPNP_VIB_LDO_VMIN_UV);
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
chip->vmax_uV = data;
|
||||
mutex_unlock(&chip->lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct device_attribute qpnp_vib_attrs =
|
||||
__ATTR(vmax_mv, 0644, qpnp_vib_show_vmax, qpnp_vib_store_vmax);
|
||||
|
||||
static int qpnp_vib_parse_dt(struct device *dev, struct vib_ldo_chip *chip)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = of_property_read_u32(dev->of_node, "qcom,vib-ldo-volt-uv",
|
||||
&chip->vmax_uV);
|
||||
if (ret < 0) {
|
||||
pr_err("qcom,vib-ldo-volt-uv property read failed, ret=%d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
chip->disable_overdrive = of_property_read_bool(dev->of_node,
|
||||
"qcom,disable-overdrive");
|
||||
|
||||
if (of_find_property(dev->of_node, "qcom,vib-overdrive-volt-uv",
|
||||
NULL)) {
|
||||
ret = of_property_read_u32(dev->of_node,
|
||||
"qcom,vib-overdrive-volt-uv",
|
||||
&chip->overdrive_volt_uV);
|
||||
if (ret < 0) {
|
||||
pr_err("qcom,vib-overdrive-volt-uv property read failed, ret=%d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* check against vibrator ldo min/max voltage limits */
|
||||
chip->overdrive_volt_uV = min(chip->overdrive_volt_uV,
|
||||
QPNP_VIB_LDO_VMAX_UV);
|
||||
chip->overdrive_volt_uV = max(chip->overdrive_volt_uV,
|
||||
QPNP_VIB_LDO_VMIN_UV);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum led_brightness qpnp_vib_brightness_get(struct led_classdev *cdev)
|
||||
{
|
||||
struct vib_ldo_chip *chip = container_of(cdev, struct vib_ldo_chip,
|
||||
cdev);
|
||||
|
||||
return chip->state;
|
||||
}
|
||||
|
||||
static int qpnp_vib_brightness_set(struct led_classdev *cdev,
|
||||
enum led_brightness level)
|
||||
{
|
||||
struct vib_ldo_chip *chip = container_of(cdev, struct vib_ldo_chip,
|
||||
cdev);
|
||||
int ret = 0;
|
||||
|
||||
chip->state = level;
|
||||
|
||||
if (chip->state) {
|
||||
ret = qpnp_vibrator_play_on(chip);
|
||||
if (ret < 0)
|
||||
pr_err("set vibrator-on failed, ret=%d\n", ret);
|
||||
} else {
|
||||
if (!chip->disable_overdrive) {
|
||||
hrtimer_cancel(&chip->overdrive_timer);
|
||||
cancel_work_sync(&chip->overdrive_work);
|
||||
}
|
||||
ret = qpnp_vib_ldo_enable(chip, false);
|
||||
}
|
||||
|
||||
pr_debug("vibrator state=%d\n", chip->state);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qpnp_vibrator_ldo_suspend(struct device *dev)
|
||||
{
|
||||
struct vib_ldo_chip *chip = dev_get_drvdata(dev);
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
if (!chip->disable_overdrive) {
|
||||
hrtimer_cancel(&chip->overdrive_timer);
|
||||
cancel_work_sync(&chip->overdrive_work);
|
||||
}
|
||||
qpnp_vib_ldo_enable(chip, false);
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
static SIMPLE_DEV_PM_OPS(qpnp_vibrator_ldo_pm_ops, qpnp_vibrator_ldo_suspend,
|
||||
NULL);
|
||||
|
||||
static int qpnp_vibrator_ldo_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *of_node = pdev->dev.of_node;
|
||||
struct vib_ldo_chip *chip;
|
||||
int ret;
|
||||
u32 base;
|
||||
|
||||
ret = of_property_read_u32(of_node, "reg", &base);
|
||||
if (ret < 0) {
|
||||
pr_err("reg property reading failed, ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
|
||||
if (!chip)
|
||||
return -ENOMEM;
|
||||
|
||||
chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
|
||||
if (!chip->regmap) {
|
||||
pr_err("couldn't get parent's regmap\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = qpnp_vib_parse_dt(&pdev->dev, chip);
|
||||
if (ret < 0) {
|
||||
pr_err("couldn't parse device tree, ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
chip->base = (uint16_t)base;
|
||||
chip->vib_play_ms = QPNP_VIB_PLAY_MS;
|
||||
mutex_init(&chip->lock);
|
||||
INIT_WORK(&chip->overdrive_work, qpnp_vib_overdrive_work);
|
||||
|
||||
hrtimer_init(&chip->overdrive_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
chip->overdrive_timer.function = vib_overdrive_timer;
|
||||
dev_set_drvdata(&pdev->dev, chip);
|
||||
|
||||
chip->cdev.name = "vibrator";
|
||||
chip->cdev.brightness_get = qpnp_vib_brightness_get;
|
||||
chip->cdev.brightness_set_blocking = qpnp_vib_brightness_set;
|
||||
chip->cdev.max_brightness = 100;
|
||||
ret = devm_led_classdev_register(&pdev->dev, &chip->cdev);
|
||||
if (ret < 0) {
|
||||
pr_err("Error in registering led class device, ret=%d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = sysfs_create_file(&chip->cdev.dev->kobj,
|
||||
&qpnp_vib_attrs.attr);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "Error in creating sysfs file, ret=%d\n",
|
||||
ret);
|
||||
goto sysfs_fail;
|
||||
}
|
||||
|
||||
pr_info("Vibrator LDO successfully registered: uV = %d, overdrive = %s\n",
|
||||
chip->vmax_uV,
|
||||
chip->disable_overdrive ? "disabled" : "enabled");
|
||||
return 0;
|
||||
|
||||
sysfs_fail:
|
||||
sysfs_remove_file(&chip->cdev.dev->kobj,
|
||||
&qpnp_vib_attrs.attr);
|
||||
fail:
|
||||
mutex_destroy(&chip->lock);
|
||||
dev_set_drvdata(&pdev->dev, NULL);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qpnp_vibrator_ldo_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct vib_ldo_chip *chip = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
if (!chip->disable_overdrive) {
|
||||
hrtimer_cancel(&chip->overdrive_timer);
|
||||
cancel_work_sync(&chip->overdrive_work);
|
||||
}
|
||||
mutex_destroy(&chip->lock);
|
||||
dev_set_drvdata(&pdev->dev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id vibrator_ldo_match_table[] = {
|
||||
{ .compatible = "qcom,qpnp-vibrator-ldo" },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, vibrator_ldo_match_table);
|
||||
|
||||
static struct platform_driver qpnp_vibrator_ldo_driver = {
|
||||
.driver = {
|
||||
.name = "qcom,qpnp-vibrator-ldo",
|
||||
.of_match_table = vibrator_ldo_match_table,
|
||||
.pm = &qpnp_vibrator_ldo_pm_ops,
|
||||
},
|
||||
.probe = qpnp_vibrator_ldo_probe,
|
||||
.remove = qpnp_vibrator_ldo_remove,
|
||||
};
|
||||
module_platform_driver(qpnp_vibrator_ldo_driver);
|
||||
|
||||
MODULE_DESCRIPTION("QPNP Vibrator-LDO driver");
|
||||
MODULE_LICENSE("GPL");
|
1960
drivers/leds/leds-qti-flash.c
Normal file
1960
drivers/leds/leds-qti-flash.c
Normal file
File diff suppressed because it is too large
Load Diff
652
drivers/leds/leds-qti-tri-led.c
Normal file
652
drivers/leds/leds-qti-tri-led.c
Normal file
@@ -0,0 +1,652 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2018-2019, 2021, The Linux Foundation. All rights reserved.
|
||||
* Copyright (c) 2022, 2024, Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/nvmem-consumer.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pwm.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/qpnp/qti-pwm.h>
|
||||
|
||||
#define TRILED_REG_TYPE 0x04
|
||||
#define TRILED_REG_SUBTYPE 0x05
|
||||
#define TRILED_REG_EN_CTL 0x46
|
||||
|
||||
/* TRILED_REG_EN_CTL */
|
||||
#define TRILED_EN_CTL_MASK GENMASK(7, 5)
|
||||
#define TRILED_EN_CTL_MAX_BIT 7
|
||||
|
||||
#define TRILED_TYPE 0x19
|
||||
#define TRILED_SUBTYPE_LED3H0L12 0x02
|
||||
#define TRILED_SUBTYPE_LED2H0L12 0x03
|
||||
#define TRILED_SUBTYPE_LED1H2L12 0x04
|
||||
|
||||
#define TRILED_NUM_MAX 3
|
||||
|
||||
#define PWM_PERIOD_DEFAULT_NS 1000000
|
||||
|
||||
struct pwm_setting {
|
||||
u64 pre_period_ns;
|
||||
u64 period_ns;
|
||||
u64 duty_ns;
|
||||
};
|
||||
|
||||
struct led_setting {
|
||||
u64 on_ms;
|
||||
u64 off_ms;
|
||||
enum led_brightness brightness;
|
||||
bool blink;
|
||||
bool breath;
|
||||
};
|
||||
|
||||
struct qpnp_led_dev {
|
||||
struct led_classdev cdev;
|
||||
struct pwm_device *pwm_dev;
|
||||
struct pwm_setting pwm_setting;
|
||||
struct led_setting led_setting;
|
||||
struct qpnp_tri_led_chip *chip;
|
||||
struct mutex lock;
|
||||
const char *label;
|
||||
const char *default_trigger;
|
||||
u8 id;
|
||||
bool blinking;
|
||||
bool breathing;
|
||||
};
|
||||
|
||||
struct qpnp_tri_led_chip {
|
||||
struct device *dev;
|
||||
struct regmap *regmap;
|
||||
struct qpnp_led_dev *leds;
|
||||
struct nvmem_device *pbs_nvmem;
|
||||
struct mutex bus_lock;
|
||||
int num_leds;
|
||||
u16 reg_base;
|
||||
u8 subtype;
|
||||
u8 bitmap;
|
||||
};
|
||||
|
||||
static int qpnp_tri_led_read(struct qpnp_tri_led_chip *chip, u16 addr, u8 *val)
|
||||
{
|
||||
int rc;
|
||||
unsigned int tmp;
|
||||
|
||||
mutex_lock(&chip->bus_lock);
|
||||
rc = regmap_read(chip->regmap, chip->reg_base + addr, &tmp);
|
||||
if (rc < 0)
|
||||
dev_err(chip->dev, "Read addr 0x%x failed, rc=%d\n", addr, rc);
|
||||
else
|
||||
*val = (u8)tmp;
|
||||
mutex_unlock(&chip->bus_lock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int qpnp_tri_led_masked_write(struct qpnp_tri_led_chip *chip,
|
||||
u16 addr, u8 mask, u8 val)
|
||||
{
|
||||
int rc;
|
||||
|
||||
mutex_lock(&chip->bus_lock);
|
||||
rc = regmap_update_bits(chip->regmap, chip->reg_base + addr, mask, val);
|
||||
if (rc < 0)
|
||||
dev_err(chip->dev, "Update addr 0x%x to val 0x%x with mask 0x%x failed, rc=%d\n",
|
||||
addr, val, mask, rc);
|
||||
mutex_unlock(&chip->bus_lock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int __tri_led_config_pwm(struct qpnp_led_dev *led,
|
||||
struct pwm_setting *pwm)
|
||||
{
|
||||
struct pwm_state pstate;
|
||||
enum pwm_output_type output_type;
|
||||
int rc;
|
||||
|
||||
pwm_get_state(led->pwm_dev, &pstate);
|
||||
pstate.enabled = !!(pwm->duty_ns != 0);
|
||||
pstate.period = pwm->period_ns;
|
||||
pstate.duty_cycle = pwm->duty_ns;
|
||||
output_type = led->led_setting.breath ?
|
||||
PWM_OUTPUT_MODULATED : PWM_OUTPUT_FIXED;
|
||||
|
||||
rc = qpnp_lpg_pwm_set_output_type(led->pwm_dev, output_type);
|
||||
if (rc < 0) {
|
||||
dev_err(led->chip->dev, "Set output_type for %s led failed, rc=%d\n",
|
||||
led->cdev.name, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = pwm_apply_state(led->pwm_dev, &pstate);
|
||||
if (rc < 0)
|
||||
dev_err(led->chip->dev, "Apply PWM state for %s led failed, rc=%d\n",
|
||||
led->cdev.name, rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#define PBS_ENABLE 1
|
||||
#define PBS_DISABLE 2
|
||||
#define PBS_ARG 0x42
|
||||
#define PBS_TRIG_CLR 0xE6
|
||||
#define PBS_TRIG_SET 0xE5
|
||||
static int __tri_led_set(struct qpnp_led_dev *led)
|
||||
{
|
||||
int rc = 0;
|
||||
u8 val = 0, mask = 0, pbs_val;
|
||||
u8 prev_bitmap;
|
||||
|
||||
rc = __tri_led_config_pwm(led, &led->pwm_setting);
|
||||
if (rc < 0) {
|
||||
dev_err(led->chip->dev, "Configure PWM for %s led failed, rc=%d\n",
|
||||
led->cdev.name, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
mask |= 1 << (TRILED_EN_CTL_MAX_BIT - led->id);
|
||||
|
||||
if (led->pwm_setting.duty_ns == 0)
|
||||
val = 0;
|
||||
else
|
||||
val = mask;
|
||||
|
||||
if (led->chip->subtype == TRILED_SUBTYPE_LED2H0L12 &&
|
||||
led->chip->pbs_nvmem) {
|
||||
/*
|
||||
* Control BOB_CONFIG_EXT_CTRL2_FORCE_EN for HR_LED through
|
||||
* PBS trigger. PBS trigger for enable happens if any one of
|
||||
* LEDs are turned on. PBS trigger for disable happens only
|
||||
* if both LEDs are turned off.
|
||||
*/
|
||||
|
||||
prev_bitmap = led->chip->bitmap;
|
||||
if (val)
|
||||
led->chip->bitmap |= (1 << led->id);
|
||||
else
|
||||
led->chip->bitmap &= ~(1 << led->id);
|
||||
|
||||
if (!(led->chip->bitmap & prev_bitmap)) {
|
||||
pbs_val = led->chip->bitmap ? PBS_ENABLE : PBS_DISABLE;
|
||||
rc = nvmem_device_write(led->chip->pbs_nvmem, PBS_ARG,
|
||||
1, &pbs_val);
|
||||
if (rc < 0) {
|
||||
dev_err(led->chip->dev, "Couldn't set PBS_ARG, rc=%d\n",
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
pbs_val = 1;
|
||||
rc = nvmem_device_write(led->chip->pbs_nvmem,
|
||||
PBS_TRIG_CLR, 1, &pbs_val);
|
||||
if (rc < 0) {
|
||||
dev_err(led->chip->dev, "Couldn't set PBS_TRIG_CLR, rc=%d\n",
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
pbs_val = 1;
|
||||
rc = nvmem_device_write(led->chip->pbs_nvmem,
|
||||
PBS_TRIG_SET, 1, &pbs_val);
|
||||
if (rc < 0) {
|
||||
dev_err(led->chip->dev, "Couldn't set PBS_TRIG_SET, rc=%d\n",
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rc = qpnp_tri_led_masked_write(led->chip, TRILED_REG_EN_CTL,
|
||||
mask, val);
|
||||
if (rc < 0)
|
||||
dev_err(led->chip->dev, "Update addr 0x%x failed, rc=%d\n",
|
||||
TRILED_REG_EN_CTL, rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int qpnp_tri_led_set(struct qpnp_led_dev *led)
|
||||
{
|
||||
u64 on_ms, off_ms, period_ns, duty_ns;
|
||||
enum led_brightness brightness = led->led_setting.brightness;
|
||||
int rc = 0;
|
||||
|
||||
if (led->led_setting.blink) {
|
||||
on_ms = led->led_setting.on_ms;
|
||||
off_ms = led->led_setting.off_ms;
|
||||
|
||||
duty_ns = on_ms * NSEC_PER_MSEC;
|
||||
period_ns = (on_ms + off_ms) * NSEC_PER_MSEC;
|
||||
|
||||
if (period_ns < duty_ns && duty_ns != 0)
|
||||
period_ns = duty_ns + 1;
|
||||
} else {
|
||||
/* Use initial period if no blinking is required */
|
||||
period_ns = led->pwm_setting.pre_period_ns;
|
||||
|
||||
if (brightness == LED_OFF)
|
||||
duty_ns = 0;
|
||||
|
||||
duty_ns = period_ns * brightness;
|
||||
do_div(duty_ns, LED_FULL);
|
||||
|
||||
if (period_ns < duty_ns && duty_ns != 0)
|
||||
period_ns = duty_ns + 1;
|
||||
}
|
||||
dev_dbg(led->chip->dev, "PWM settings for %s led: period = %lluns, duty = %lluns\n",
|
||||
led->cdev.name, period_ns, duty_ns);
|
||||
|
||||
led->pwm_setting.duty_ns = duty_ns;
|
||||
led->pwm_setting.period_ns = period_ns;
|
||||
|
||||
rc = __tri_led_set(led);
|
||||
if (rc < 0) {
|
||||
dev_err(led->chip->dev, "__tri_led_set %s failed, rc=%d\n",
|
||||
led->cdev.name, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (led->led_setting.blink) {
|
||||
led->cdev.brightness = LED_FULL;
|
||||
led->blinking = true;
|
||||
led->breathing = false;
|
||||
} else if (led->led_setting.breath) {
|
||||
led->cdev.brightness = LED_FULL;
|
||||
led->blinking = false;
|
||||
led->breathing = true;
|
||||
} else {
|
||||
led->cdev.brightness = led->led_setting.brightness;
|
||||
led->blinking = false;
|
||||
led->breathing = false;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int qpnp_tri_led_set_brightness(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct qpnp_led_dev *led =
|
||||
container_of(led_cdev, struct qpnp_led_dev, cdev);
|
||||
int rc = 0;
|
||||
|
||||
mutex_lock(&led->lock);
|
||||
if (brightness > LED_FULL)
|
||||
brightness = LED_FULL;
|
||||
|
||||
if (brightness == led->led_setting.brightness &&
|
||||
!led->blinking && !led->breathing) {
|
||||
mutex_unlock(&led->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
led->led_setting.brightness = brightness;
|
||||
if (!!brightness)
|
||||
led->led_setting.off_ms = 0;
|
||||
else
|
||||
led->led_setting.on_ms = 0;
|
||||
led->led_setting.blink = false;
|
||||
led->led_setting.breath = false;
|
||||
|
||||
rc = qpnp_tri_led_set(led);
|
||||
if (rc)
|
||||
dev_err(led->chip->dev, "Set led failed for %s, rc=%d\n",
|
||||
led->label, rc);
|
||||
|
||||
mutex_unlock(&led->lock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static enum led_brightness qpnp_tri_led_get_brightness(
|
||||
struct led_classdev *led_cdev)
|
||||
{
|
||||
return led_cdev->brightness;
|
||||
}
|
||||
|
||||
static int qpnp_tri_led_set_blink(struct led_classdev *led_cdev,
|
||||
unsigned long *on_ms, unsigned long *off_ms)
|
||||
{
|
||||
struct qpnp_led_dev *led =
|
||||
container_of(led_cdev, struct qpnp_led_dev, cdev);
|
||||
int rc = 0;
|
||||
|
||||
mutex_lock(&led->lock);
|
||||
if (led->blinking && *on_ms == led->led_setting.on_ms &&
|
||||
*off_ms == led->led_setting.off_ms) {
|
||||
dev_dbg(led_cdev->dev, "Ignore, on/off setting is not changed: on %lums, off %lums\n",
|
||||
*on_ms, *off_ms);
|
||||
mutex_unlock(&led->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (*on_ms == 0) {
|
||||
led->led_setting.blink = false;
|
||||
led->led_setting.breath = false;
|
||||
led->led_setting.brightness = LED_OFF;
|
||||
} else if (*off_ms == 0) {
|
||||
led->led_setting.blink = false;
|
||||
led->led_setting.breath = false;
|
||||
led->led_setting.brightness = led->cdev.brightness;
|
||||
} else {
|
||||
led->led_setting.on_ms = *on_ms;
|
||||
led->led_setting.off_ms = *off_ms;
|
||||
led->led_setting.blink = true;
|
||||
led->led_setting.breath = false;
|
||||
}
|
||||
|
||||
rc = qpnp_tri_led_set(led);
|
||||
if (rc)
|
||||
dev_err(led->chip->dev, "Set led failed for %s, rc=%d\n",
|
||||
led->label, rc);
|
||||
|
||||
mutex_unlock(&led->lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static ssize_t breath_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct qpnp_led_dev *led =
|
||||
container_of(led_cdev, struct qpnp_led_dev, cdev);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n", led->led_setting.breath);
|
||||
}
|
||||
|
||||
static ssize_t breath_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int rc;
|
||||
bool breath;
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct qpnp_led_dev *led =
|
||||
container_of(led_cdev, struct qpnp_led_dev, cdev);
|
||||
|
||||
rc = kstrtobool(buf, &breath);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
cancel_work_sync(&led_cdev->set_brightness_work);
|
||||
|
||||
mutex_lock(&led->lock);
|
||||
if (led->breathing == breath)
|
||||
goto unlock;
|
||||
|
||||
led->led_setting.blink = false;
|
||||
led->led_setting.breath = breath;
|
||||
led->led_setting.brightness = breath ? LED_FULL : LED_OFF;
|
||||
rc = qpnp_tri_led_set(led);
|
||||
if (rc < 0)
|
||||
dev_err(led->chip->dev, "Set led failed for %s, rc=%d\n",
|
||||
led->label, rc);
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&led->lock);
|
||||
return (rc < 0) ? rc : count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RW(breath);
|
||||
static const struct attribute *breath_attrs[] = {
|
||||
&dev_attr_breath.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static int qpnp_tri_led_register(struct qpnp_tri_led_chip *chip)
|
||||
{
|
||||
struct qpnp_led_dev *led;
|
||||
int rc, i, j;
|
||||
|
||||
for (i = 0; i < chip->num_leds; i++) {
|
||||
led = &chip->leds[i];
|
||||
mutex_init(&led->lock);
|
||||
led->cdev.name = led->label;
|
||||
led->cdev.max_brightness = LED_FULL;
|
||||
led->cdev.brightness_set_blocking = qpnp_tri_led_set_brightness;
|
||||
led->cdev.brightness_get = qpnp_tri_led_get_brightness;
|
||||
led->cdev.blink_set = qpnp_tri_led_set_blink;
|
||||
led->cdev.default_trigger = led->default_trigger;
|
||||
led->cdev.brightness = LED_OFF;
|
||||
|
||||
rc = devm_led_classdev_register(chip->dev, &led->cdev);
|
||||
if (rc < 0) {
|
||||
dev_err(chip->dev, "%s led class device registering failed, rc=%d\n",
|
||||
led->label, rc);
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
rc = qpnp_lpg_pwm_get_output_types_supported(led->pwm_dev);
|
||||
if (rc > 0 && (rc & PWM_OUTPUT_MODULATED)) {
|
||||
rc = sysfs_create_files(&led->cdev.dev->kobj,
|
||||
breath_attrs);
|
||||
if (rc < 0) {
|
||||
dev_err(chip->dev, "Create breath file for %s led failed, rc=%d\n",
|
||||
led->label, rc);
|
||||
goto err_out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_out:
|
||||
for (j = 0; j <= i; j++) {
|
||||
if (j < i)
|
||||
sysfs_remove_files(&chip->leds[j].cdev.dev->kobj,
|
||||
breath_attrs);
|
||||
mutex_destroy(&chip->leds[j].lock);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int qpnp_tri_led_hw_init(struct qpnp_tri_led_chip *chip)
|
||||
{
|
||||
int rc = 0;
|
||||
u8 val;
|
||||
|
||||
rc = qpnp_tri_led_read(chip, TRILED_REG_TYPE, &val);
|
||||
if (rc < 0) {
|
||||
dev_err(chip->dev, "Read REG_TYPE failed, rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (val != TRILED_TYPE) {
|
||||
dev_err(chip->dev, "invalid subtype(%d)\n", val);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
rc = qpnp_tri_led_read(chip, TRILED_REG_SUBTYPE, &val);
|
||||
if (rc < 0) {
|
||||
dev_err(chip->dev, "Read REG_SUBTYPE failed, rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
chip->subtype = val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qpnp_tri_led_parse_dt(struct qpnp_tri_led_chip *chip)
|
||||
{
|
||||
struct device_node *node = chip->dev->of_node, *child_node;
|
||||
struct qpnp_led_dev *led;
|
||||
struct pwm_args pargs;
|
||||
const __be32 *addr;
|
||||
int rc = 0, id, i = 0;
|
||||
|
||||
addr = of_get_address(chip->dev->of_node, 0, NULL, NULL);
|
||||
if (!addr) {
|
||||
dev_err(chip->dev, "Getting address failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
chip->reg_base = be32_to_cpu(addr[0]);
|
||||
|
||||
chip->num_leds = of_get_available_child_count(node);
|
||||
if (chip->num_leds == 0) {
|
||||
dev_err(chip->dev, "No led child node defined\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (chip->num_leds > TRILED_NUM_MAX) {
|
||||
dev_err(chip->dev, "can't support %d leds(max %d)\n",
|
||||
chip->num_leds, TRILED_NUM_MAX);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (of_find_property(chip->dev->of_node, "nvmem", NULL)) {
|
||||
chip->pbs_nvmem = devm_nvmem_device_get(chip->dev, "pbs_sdam");
|
||||
if (IS_ERR_OR_NULL(chip->pbs_nvmem)) {
|
||||
rc = PTR_ERR(chip->pbs_nvmem);
|
||||
if (rc != -EPROBE_DEFER) {
|
||||
dev_err(chip->dev, "Couldn't get nvmem device, rc=%d\n",
|
||||
rc);
|
||||
return -ENODEV;
|
||||
}
|
||||
chip->pbs_nvmem = NULL;
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
chip->leds = devm_kcalloc(chip->dev, chip->num_leds,
|
||||
sizeof(struct qpnp_led_dev), GFP_KERNEL);
|
||||
if (!chip->leds)
|
||||
return -ENOMEM;
|
||||
|
||||
for_each_available_child_of_node(node, child_node) {
|
||||
rc = of_property_read_u32(child_node, "led-sources", &id);
|
||||
if (rc) {
|
||||
dev_err(chip->dev, "Get led-sources failed, rc=%d\n",
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (id >= TRILED_NUM_MAX) {
|
||||
dev_err(chip->dev, "only support 0~%d current source\n",
|
||||
TRILED_NUM_MAX - 1);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
led = &chip->leds[i++];
|
||||
led->chip = chip;
|
||||
led->id = id;
|
||||
led->label =
|
||||
of_get_property(child_node, "label", NULL) ? :
|
||||
child_node->name;
|
||||
|
||||
led->pwm_dev =
|
||||
devm_fwnode_pwm_get(chip->dev, of_fwnode_handle(child_node), NULL);
|
||||
if (IS_ERR(led->pwm_dev)) {
|
||||
rc = PTR_ERR(led->pwm_dev);
|
||||
if (rc != -EPROBE_DEFER)
|
||||
dev_err(chip->dev, "Get pwm device for %s led failed, rc=%d\n",
|
||||
led->label, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
pwm_get_args(led->pwm_dev, &pargs);
|
||||
if (pargs.period == 0)
|
||||
led->pwm_setting.pre_period_ns = PWM_PERIOD_DEFAULT_NS;
|
||||
else
|
||||
led->pwm_setting.pre_period_ns = pargs.period;
|
||||
|
||||
led->default_trigger = of_get_property(child_node,
|
||||
"linux,default-trigger", NULL);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int qpnp_tri_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct qpnp_tri_led_chip *chip;
|
||||
int rc = 0;
|
||||
|
||||
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
|
||||
if (!chip)
|
||||
return -ENOMEM;
|
||||
|
||||
chip->dev = &pdev->dev;
|
||||
chip->regmap = dev_get_regmap(chip->dev->parent, NULL);
|
||||
if (!chip->regmap) {
|
||||
dev_err(chip->dev, "Getting regmap failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = qpnp_tri_led_parse_dt(chip);
|
||||
if (rc < 0) {
|
||||
if (rc != -EPROBE_DEFER)
|
||||
dev_err(chip->dev, "Devicetree properties parsing failed, rc=%d\n",
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
mutex_init(&chip->bus_lock);
|
||||
|
||||
rc = qpnp_tri_led_hw_init(chip);
|
||||
if (rc) {
|
||||
dev_err(chip->dev, "HW initialization failed, rc=%d\n", rc);
|
||||
goto destroy;
|
||||
}
|
||||
|
||||
dev_set_drvdata(chip->dev, chip);
|
||||
rc = qpnp_tri_led_register(chip);
|
||||
if (rc < 0) {
|
||||
dev_err(chip->dev, "Registering LED class devices failed, rc=%d\n",
|
||||
rc);
|
||||
goto destroy;
|
||||
}
|
||||
|
||||
dev_dbg(chip->dev, "Tri-led module with subtype 0x%x is detected\n",
|
||||
chip->subtype);
|
||||
return 0;
|
||||
destroy:
|
||||
mutex_destroy(&chip->bus_lock);
|
||||
dev_set_drvdata(chip->dev, NULL);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int qpnp_tri_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
int i;
|
||||
struct qpnp_tri_led_chip *chip = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
mutex_destroy(&chip->bus_lock);
|
||||
for (i = 0; i < chip->num_leds; i++) {
|
||||
sysfs_remove_files(&chip->leds[i].cdev.dev->kobj, breath_attrs);
|
||||
mutex_destroy(&chip->leds[i].lock);
|
||||
}
|
||||
dev_set_drvdata(chip->dev, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id qpnp_tri_led_of_match[] = {
|
||||
{ .compatible = "qcom,tri-led",},
|
||||
{ },
|
||||
};
|
||||
|
||||
static struct platform_driver qpnp_tri_led_driver = {
|
||||
.driver = {
|
||||
.name = "leds-qti-tri-led",
|
||||
.of_match_table = qpnp_tri_led_of_match,
|
||||
},
|
||||
.probe = qpnp_tri_led_probe,
|
||||
.remove = qpnp_tri_led_remove,
|
||||
};
|
||||
module_platform_driver(qpnp_tri_led_driver);
|
||||
|
||||
MODULE_DESCRIPTION("QTI TRI_LED driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_SOFTDEP("pre: pwm-qti-lpg");
|
@@ -8,11 +8,13 @@
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/led-class-multicolor.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/nvmem-consumer.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pwm.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/soc/qcom/qcom-pbs.h>
|
||||
|
||||
#define LPG_SUBTYPE_REG 0x05
|
||||
#define LPG_SUBTYPE_LPG 0x2
|
||||
@@ -39,6 +41,10 @@
|
||||
#define PWM_SEC_ACCESS_REG 0xd0
|
||||
#define PWM_DTEST_REG(x) (0xe2 + (x) - 1)
|
||||
|
||||
#define SDAM_REG_PBS_SEQ_EN 0x42
|
||||
#define SDAM_PBS_TRIG_SET 0xe5
|
||||
#define SDAM_PBS_TRIG_CLR 0xe6
|
||||
|
||||
#define TRI_LED_SRC_SEL 0x45
|
||||
#define TRI_LED_EN_CTL 0x46
|
||||
#define TRI_LED_ATC_CTL 0x47
|
||||
@@ -48,9 +54,31 @@
|
||||
|
||||
#define LPG_RESOLUTION_9BIT BIT(9)
|
||||
#define LPG_RESOLUTION_15BIT BIT(15)
|
||||
#define PPG_MAX_LED_BRIGHTNESS 255
|
||||
|
||||
#define LPG_MAX_M 7
|
||||
#define LPG_MAX_PREDIV 6
|
||||
|
||||
#define DEFAULT_TICK_DURATION_US 7800
|
||||
#define RAMP_STEP_DURATION(x) (((x) * 1000 / DEFAULT_TICK_DURATION_US) & 0xff)
|
||||
|
||||
#define SDAM_MAX_DEVICES 2
|
||||
/* LPG common config settings for PPG */
|
||||
#define SDAM_START_BASE 0x40
|
||||
#define SDAM_REG_RAMP_STEP_DURATION 0x47
|
||||
|
||||
#define SDAM_LUT_SDAM_LUT_PATTERN_OFFSET 0x45
|
||||
#define SDAM_LPG_SDAM_LUT_PATTERN_OFFSET 0x80
|
||||
|
||||
/* LPG per channel config settings for PPG */
|
||||
#define SDAM_LUT_EN_OFFSET 0x0
|
||||
#define SDAM_PATTERN_CONFIG_OFFSET 0x1
|
||||
#define SDAM_END_INDEX_OFFSET 0x3
|
||||
#define SDAM_START_INDEX_OFFSET 0x4
|
||||
#define SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET 0x6
|
||||
#define SDAM_PAUSE_HI_MULTIPLIER_OFFSET 0x8
|
||||
#define SDAM_PAUSE_LO_MULTIPLIER_OFFSET 0x9
|
||||
|
||||
struct lpg_channel;
|
||||
struct lpg_data;
|
||||
|
||||
@@ -64,6 +92,10 @@ struct lpg_data;
|
||||
* @lut_base: base address of the LUT block (optional)
|
||||
* @lut_size: number of entries in the LUT block
|
||||
* @lut_bitmap: allocation bitmap for LUT entries
|
||||
* @pbs_dev: PBS device
|
||||
* @lpg_chan_sdam: LPG SDAM peripheral device
|
||||
* @lut_sdam: LUT SDAM peripheral device
|
||||
* @pbs_en_bitmap: bitmap for tracking PBS triggers
|
||||
* @triled_base: base address of the TRILED block (optional)
|
||||
* @triled_src: power-source for the TRILED
|
||||
* @triled_has_atc_ctl: true if there is TRI_LED_ATC_CTL register
|
||||
@@ -85,6 +117,11 @@ struct lpg {
|
||||
u32 lut_size;
|
||||
unsigned long *lut_bitmap;
|
||||
|
||||
struct pbs_dev *pbs_dev;
|
||||
struct nvmem_device *lpg_chan_sdam;
|
||||
struct nvmem_device *lut_sdam;
|
||||
unsigned long pbs_en_bitmap;
|
||||
|
||||
u32 triled_base;
|
||||
u32 triled_src;
|
||||
bool triled_has_atc_ctl;
|
||||
@@ -101,6 +138,7 @@ struct lpg {
|
||||
* @triled_mask: mask in TRILED to enable this channel
|
||||
* @lut_mask: mask in LUT to start pattern generator for this channel
|
||||
* @subtype: PMIC hardware block subtype
|
||||
* @sdam_offset: channel offset in LPG SDAM
|
||||
* @in_use: channel is exposed to LED framework
|
||||
* @color: color of the LED attached to this channel
|
||||
* @dtest_line: DTEST line for output, or 0 if disabled
|
||||
@@ -129,6 +167,7 @@ struct lpg_channel {
|
||||
unsigned int triled_mask;
|
||||
unsigned int lut_mask;
|
||||
unsigned int subtype;
|
||||
u32 sdam_offset;
|
||||
|
||||
bool in_use;
|
||||
|
||||
@@ -178,10 +217,12 @@ struct lpg_led {
|
||||
|
||||
/**
|
||||
* struct lpg_channel_data - per channel initialization data
|
||||
* @sdam_offset: Channel offset in LPG SDAM
|
||||
* @base: base address for PWM channel registers
|
||||
* @triled_mask: bitmask for controlling this channel in TRILED
|
||||
*/
|
||||
struct lpg_channel_data {
|
||||
unsigned int sdam_offset;
|
||||
unsigned int base;
|
||||
u8 triled_mask;
|
||||
};
|
||||
@@ -206,6 +247,65 @@ struct lpg_data {
|
||||
const struct lpg_channel_data *channels;
|
||||
};
|
||||
|
||||
#define PBS_SW_TRIG_BIT BIT(0)
|
||||
|
||||
static int lpg_clear_pbs_trigger(struct lpg *lpg, unsigned int lut_mask)
|
||||
{
|
||||
u8 val = 0;
|
||||
int rc;
|
||||
|
||||
lpg->pbs_en_bitmap &= (~lut_mask);
|
||||
if (!lpg->pbs_en_bitmap) {
|
||||
rc = nvmem_device_write(lpg->lpg_chan_sdam, SDAM_REG_PBS_SEQ_EN, 1, &val);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
if (lpg->lut_sdam) {
|
||||
val = PBS_SW_TRIG_BIT;
|
||||
rc = nvmem_device_write(lpg->lpg_chan_sdam, SDAM_PBS_TRIG_CLR, 1, &val);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpg_set_pbs_trigger(struct lpg *lpg, unsigned int lut_mask)
|
||||
{
|
||||
u8 val = PBS_SW_TRIG_BIT;
|
||||
int rc;
|
||||
|
||||
if (!lpg->pbs_en_bitmap) {
|
||||
rc = nvmem_device_write(lpg->lpg_chan_sdam, SDAM_REG_PBS_SEQ_EN, 1, &val);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
if (lpg->lut_sdam) {
|
||||
rc = nvmem_device_write(lpg->lpg_chan_sdam, SDAM_PBS_TRIG_SET, 1, &val);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
} else {
|
||||
rc = qcom_pbs_trigger_event(lpg->pbs_dev, val);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
lpg->pbs_en_bitmap |= lut_mask;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpg_sdam_configure_triggers(struct lpg_channel *chan, u8 set_trig)
|
||||
{
|
||||
u32 addr = SDAM_LUT_EN_OFFSET + chan->sdam_offset;
|
||||
|
||||
if (!chan->lpg->lpg_chan_sdam)
|
||||
return 0;
|
||||
|
||||
return nvmem_device_write(chan->lpg->lpg_chan_sdam, addr, 1, &set_trig);
|
||||
}
|
||||
|
||||
static int triled_set(struct lpg *lpg, unsigned int mask, unsigned int enable)
|
||||
{
|
||||
/* Skip if we don't have a triled block */
|
||||
@@ -216,6 +316,47 @@ static int triled_set(struct lpg *lpg, unsigned int mask, unsigned int enable)
|
||||
mask, enable);
|
||||
}
|
||||
|
||||
static int lpg_lut_store_sdam(struct lpg *lpg, struct led_pattern *pattern,
|
||||
size_t len, unsigned int *lo_idx, unsigned int *hi_idx)
|
||||
{
|
||||
unsigned int idx;
|
||||
u8 brightness;
|
||||
int i, rc;
|
||||
u16 addr;
|
||||
|
||||
if (len > lpg->lut_size) {
|
||||
dev_err(lpg->dev, "Pattern length (%zu) exceeds maximum pattern length (%d)\n",
|
||||
len, lpg->lut_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
idx = bitmap_find_next_zero_area(lpg->lut_bitmap, lpg->lut_size, 0, len, 0);
|
||||
if (idx >= lpg->lut_size)
|
||||
return -ENOSPC;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
brightness = pattern[i].brightness;
|
||||
|
||||
if (lpg->lut_sdam) {
|
||||
addr = SDAM_LUT_SDAM_LUT_PATTERN_OFFSET + i + idx;
|
||||
rc = nvmem_device_write(lpg->lut_sdam, addr, 1, &brightness);
|
||||
} else {
|
||||
addr = SDAM_LPG_SDAM_LUT_PATTERN_OFFSET + i + idx;
|
||||
rc = nvmem_device_write(lpg->lpg_chan_sdam, addr, 1, &brightness);
|
||||
}
|
||||
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
}
|
||||
|
||||
bitmap_set(lpg->lut_bitmap, idx, len);
|
||||
|
||||
*lo_idx = idx;
|
||||
*hi_idx = idx + len - 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpg_lut_store(struct lpg *lpg, struct led_pattern *pattern,
|
||||
size_t len, unsigned int *lo_idx, unsigned int *hi_idx)
|
||||
{
|
||||
@@ -256,6 +397,9 @@ static void lpg_lut_free(struct lpg *lpg, unsigned int lo_idx, unsigned int hi_i
|
||||
|
||||
static int lpg_lut_sync(struct lpg *lpg, unsigned int mask)
|
||||
{
|
||||
if (!lpg->lut_base)
|
||||
return 0;
|
||||
|
||||
return regmap_write(lpg->map, lpg->lut_base + RAMP_CONTROL_REG, mask);
|
||||
}
|
||||
|
||||
@@ -462,6 +606,49 @@ static void lpg_apply_pwm_value(struct lpg_channel *chan)
|
||||
#define LPG_PATTERN_CONFIG_PAUSE_HI BIT(1)
|
||||
#define LPG_PATTERN_CONFIG_PAUSE_LO BIT(0)
|
||||
|
||||
static void lpg_sdam_apply_lut_control(struct lpg_channel *chan)
|
||||
{
|
||||
struct nvmem_device *lpg_chan_sdam = chan->lpg->lpg_chan_sdam;
|
||||
unsigned int lo_idx = chan->pattern_lo_idx;
|
||||
unsigned int hi_idx = chan->pattern_hi_idx;
|
||||
u8 val = 0, conf = 0, lut_offset = 0;
|
||||
unsigned int hi_pause, lo_pause;
|
||||
struct lpg *lpg = chan->lpg;
|
||||
|
||||
if (!chan->ramp_enabled || chan->pattern_lo_idx == chan->pattern_hi_idx)
|
||||
return;
|
||||
|
||||
hi_pause = DIV_ROUND_UP(chan->ramp_hi_pause_ms, chan->ramp_tick_ms);
|
||||
lo_pause = DIV_ROUND_UP(chan->ramp_lo_pause_ms, chan->ramp_tick_ms);
|
||||
|
||||
if (!chan->ramp_oneshot)
|
||||
conf |= LPG_PATTERN_CONFIG_REPEAT;
|
||||
if (chan->ramp_hi_pause_ms && lpg->lut_sdam)
|
||||
conf |= LPG_PATTERN_CONFIG_PAUSE_HI;
|
||||
if (chan->ramp_lo_pause_ms && lpg->lut_sdam)
|
||||
conf |= LPG_PATTERN_CONFIG_PAUSE_LO;
|
||||
|
||||
if (lpg->lut_sdam) {
|
||||
lut_offset = SDAM_LUT_SDAM_LUT_PATTERN_OFFSET - SDAM_START_BASE;
|
||||
hi_idx += lut_offset;
|
||||
lo_idx += lut_offset;
|
||||
}
|
||||
|
||||
nvmem_device_write(lpg_chan_sdam, SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET + chan->sdam_offset, 1, &val);
|
||||
nvmem_device_write(lpg_chan_sdam, SDAM_PATTERN_CONFIG_OFFSET + chan->sdam_offset, 1, &conf);
|
||||
nvmem_device_write(lpg_chan_sdam, SDAM_END_INDEX_OFFSET + chan->sdam_offset, 1, &hi_idx);
|
||||
nvmem_device_write(lpg_chan_sdam, SDAM_START_INDEX_OFFSET + chan->sdam_offset, 1, &lo_idx);
|
||||
|
||||
val = RAMP_STEP_DURATION(chan->ramp_tick_ms);
|
||||
nvmem_device_write(lpg_chan_sdam, SDAM_REG_RAMP_STEP_DURATION, 1, &val);
|
||||
|
||||
if (lpg->lut_sdam) {
|
||||
nvmem_device_write(lpg_chan_sdam, SDAM_PAUSE_HI_MULTIPLIER_OFFSET + chan->sdam_offset, 1, &hi_pause);
|
||||
nvmem_device_write(lpg_chan_sdam, SDAM_PAUSE_LO_MULTIPLIER_OFFSET + chan->sdam_offset, 1, &lo_pause);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void lpg_apply_lut_control(struct lpg_channel *chan)
|
||||
{
|
||||
struct lpg *lpg = chan->lpg;
|
||||
@@ -597,7 +784,10 @@ static void lpg_apply(struct lpg_channel *chan)
|
||||
lpg_apply_pwm_value(chan);
|
||||
lpg_apply_control(chan);
|
||||
lpg_apply_sync(chan);
|
||||
lpg_apply_lut_control(chan);
|
||||
if (chan->lpg->lpg_chan_sdam)
|
||||
lpg_sdam_apply_lut_control(chan);
|
||||
else
|
||||
lpg_apply_lut_control(chan);
|
||||
lpg_enable_glitch(chan);
|
||||
}
|
||||
|
||||
@@ -622,6 +812,7 @@ static void lpg_brightness_set(struct lpg_led *led, struct led_classdev *cdev,
|
||||
chan->ramp_enabled = false;
|
||||
} else if (chan->pattern_lo_idx != chan->pattern_hi_idx) {
|
||||
lpg_calc_freq(chan, NSEC_PER_MSEC);
|
||||
lpg_sdam_configure_triggers(chan, 1);
|
||||
|
||||
chan->enabled = true;
|
||||
chan->ramp_enabled = true;
|
||||
@@ -649,8 +840,10 @@ static void lpg_brightness_set(struct lpg_led *led, struct led_classdev *cdev,
|
||||
triled_set(lpg, triled_mask, triled_enabled);
|
||||
|
||||
/* Trigger start of ramp generator(s) */
|
||||
if (lut_mask)
|
||||
if (lut_mask) {
|
||||
lpg_lut_sync(lpg, lut_mask);
|
||||
lpg_set_pbs_trigger(lpg, lut_mask);
|
||||
}
|
||||
}
|
||||
|
||||
static int lpg_brightness_single_set(struct led_classdev *cdev,
|
||||
@@ -767,9 +960,9 @@ static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern,
|
||||
struct led_pattern *pattern;
|
||||
unsigned int brightness_a;
|
||||
unsigned int brightness_b;
|
||||
unsigned int hi_pause = 0;
|
||||
unsigned int lo_pause = 0;
|
||||
unsigned int actual_len;
|
||||
unsigned int hi_pause;
|
||||
unsigned int lo_pause;
|
||||
unsigned int delta_t;
|
||||
unsigned int lo_idx;
|
||||
unsigned int hi_idx;
|
||||
@@ -836,18 +1029,24 @@ static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern,
|
||||
* If the specified pattern is a palindrome the ping pong mode is
|
||||
* enabled. In this scenario the delta_t of the middle entry (i.e. the
|
||||
* last in the programmed pattern) determines the "high pause".
|
||||
*
|
||||
* SDAM-based devices do not support "ping pong", and only supports
|
||||
* "low pause" and "high pause" with a dedicated SDAM LUT.
|
||||
*/
|
||||
|
||||
/* Detect palindromes and use "ping pong" to reduce LUT usage */
|
||||
for (i = 0; i < len / 2; i++) {
|
||||
brightness_a = pattern[i].brightness;
|
||||
brightness_b = pattern[len - i - 1].brightness;
|
||||
if (lpg->lut_base) {
|
||||
for (i = 0; i < len / 2; i++) {
|
||||
brightness_a = pattern[i].brightness;
|
||||
brightness_b = pattern[len - i - 1].brightness;
|
||||
|
||||
if (brightness_a != brightness_b) {
|
||||
ping_pong = false;
|
||||
break;
|
||||
if (brightness_a != brightness_b) {
|
||||
ping_pong = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else
|
||||
ping_pong = false;
|
||||
|
||||
/* The pattern length to be written to the LUT */
|
||||
if (ping_pong)
|
||||
@@ -875,12 +1074,27 @@ static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern,
|
||||
if (delta_t >= BIT(9))
|
||||
goto out_free_pattern;
|
||||
|
||||
/* Find "low pause" and "high pause" in the pattern */
|
||||
lo_pause = pattern[0].delta_t;
|
||||
hi_pause = pattern[actual_len - 1].delta_t;
|
||||
/*
|
||||
* Find "low pause" and "high pause" in the pattern in the LUT case.
|
||||
* SDAM-based devices without dedicated LUT SDAM require equal
|
||||
* duration of all steps.
|
||||
*/
|
||||
if (lpg->lut_base || lpg->lut_sdam) {
|
||||
lo_pause = pattern[0].delta_t;
|
||||
hi_pause = pattern[actual_len - 1].delta_t;
|
||||
} else {
|
||||
if (delta_t != pattern[0].delta_t || delta_t != pattern[actual_len - 1].delta_t)
|
||||
goto out_free_pattern;
|
||||
}
|
||||
|
||||
|
||||
mutex_lock(&lpg->lock);
|
||||
ret = lpg_lut_store(lpg, pattern, actual_len, &lo_idx, &hi_idx);
|
||||
|
||||
if (lpg->lut_base)
|
||||
ret = lpg_lut_store(lpg, pattern, actual_len, &lo_idx, &hi_idx);
|
||||
else
|
||||
ret = lpg_lut_store_sdam(lpg, pattern, actual_len, &lo_idx, &hi_idx);
|
||||
|
||||
if (ret < 0)
|
||||
goto out_unlock;
|
||||
|
||||
@@ -928,7 +1142,12 @@ static int lpg_pattern_mc_set(struct led_classdev *cdev,
|
||||
{
|
||||
struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
|
||||
struct lpg_led *led = container_of(mc, struct lpg_led, mcdev);
|
||||
int ret;
|
||||
unsigned int triled_mask = 0;
|
||||
int ret, i;
|
||||
|
||||
for (i = 0; i < led->num_channels; i++)
|
||||
triled_mask |= led->channels[i]->triled_mask;
|
||||
triled_set(led->lpg, triled_mask, 0);
|
||||
|
||||
ret = lpg_pattern_set(led, pattern, len, repeat);
|
||||
if (ret < 0)
|
||||
@@ -953,6 +1172,8 @@ static int lpg_pattern_clear(struct lpg_led *led)
|
||||
|
||||
for (i = 0; i < led->num_channels; i++) {
|
||||
chan = led->channels[i];
|
||||
lpg_sdam_configure_triggers(chan, 0);
|
||||
lpg_clear_pbs_trigger(chan->lpg, chan->lut_mask);
|
||||
chan->pattern_lo_idx = 0;
|
||||
chan->pattern_hi_idx = 0;
|
||||
}
|
||||
@@ -1188,8 +1409,8 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
|
||||
cdev->brightness_set_blocking = lpg_brightness_mc_set;
|
||||
cdev->blink_set = lpg_blink_mc_set;
|
||||
|
||||
/* Register pattern accessors only if we have a LUT block */
|
||||
if (lpg->lut_base) {
|
||||
/* Register pattern accessors if we have a LUT block or when using PPG */
|
||||
if (lpg->lut_base || lpg->lpg_chan_sdam) {
|
||||
cdev->pattern_set = lpg_pattern_mc_set;
|
||||
cdev->pattern_clear = lpg_pattern_mc_clear;
|
||||
}
|
||||
@@ -1202,15 +1423,19 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
|
||||
cdev->brightness_set_blocking = lpg_brightness_single_set;
|
||||
cdev->blink_set = lpg_blink_single_set;
|
||||
|
||||
/* Register pattern accessors only if we have a LUT block */
|
||||
if (lpg->lut_base) {
|
||||
/* Register pattern accessors if we have a LUT block or when using PPG */
|
||||
if (lpg->lut_base || lpg->lpg_chan_sdam) {
|
||||
cdev->pattern_set = lpg_pattern_single_set;
|
||||
cdev->pattern_clear = lpg_pattern_single_clear;
|
||||
}
|
||||
}
|
||||
|
||||
cdev->default_trigger = of_get_property(np, "linux,default-trigger", NULL);
|
||||
cdev->max_brightness = LPG_RESOLUTION_9BIT - 1;
|
||||
|
||||
if (lpg->lpg_chan_sdam)
|
||||
cdev->max_brightness = PPG_MAX_LED_BRIGHTNESS;
|
||||
else
|
||||
cdev->max_brightness = LPG_RESOLUTION_9BIT - 1;
|
||||
|
||||
if (!of_property_read_string(np, "default-state", &state) &&
|
||||
!strcmp(state, "on"))
|
||||
@@ -1251,6 +1476,7 @@ static int lpg_init_channels(struct lpg *lpg)
|
||||
chan->base = data->channels[i].base;
|
||||
chan->triled_mask = data->channels[i].triled_mask;
|
||||
chan->lut_mask = BIT(i);
|
||||
chan->sdam_offset = data->channels[i].sdam_offset;
|
||||
|
||||
regmap_read(lpg->map, chan->base + LPG_SUBTYPE_REG, &chan->subtype);
|
||||
}
|
||||
@@ -1297,11 +1523,12 @@ static int lpg_init_lut(struct lpg *lpg)
|
||||
{
|
||||
const struct lpg_data *data = lpg->data;
|
||||
|
||||
if (!data->lut_base)
|
||||
if (!data->lut_size)
|
||||
return 0;
|
||||
|
||||
lpg->lut_base = data->lut_base;
|
||||
lpg->lut_size = data->lut_size;
|
||||
if (data->lut_base)
|
||||
lpg->lut_base = data->lut_base;
|
||||
|
||||
lpg->lut_bitmap = devm_bitmap_zalloc(lpg->dev, lpg->lut_size, GFP_KERNEL);
|
||||
if (!lpg->lut_bitmap)
|
||||
@@ -1310,6 +1537,59 @@ static int lpg_init_lut(struct lpg *lpg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpg_init_sdam(struct lpg *lpg)
|
||||
{
|
||||
int i, sdam_count, rc;
|
||||
u8 val = 0;
|
||||
|
||||
sdam_count = of_property_count_strings(lpg->dev->of_node, "nvmem-names");
|
||||
if (sdam_count <= 0)
|
||||
return 0;
|
||||
if (sdam_count > SDAM_MAX_DEVICES)
|
||||
return -EINVAL;
|
||||
|
||||
/* Get the 1st SDAM device for LPG/LUT config */
|
||||
lpg->lpg_chan_sdam = devm_nvmem_device_get(lpg->dev, "lpg_chan_sdam");
|
||||
if (IS_ERR(lpg->lpg_chan_sdam))
|
||||
return dev_err_probe(lpg->dev, PTR_ERR(lpg->lpg_chan_sdam),
|
||||
"Failed to get LPG chan SDAM device\n");
|
||||
|
||||
if (sdam_count == 1) {
|
||||
/* Get PBS device node if single SDAM device */
|
||||
lpg->pbs_dev = get_pbs_client_device(lpg->dev);
|
||||
if (IS_ERR(lpg->pbs_dev))
|
||||
return dev_err_probe(lpg->dev, PTR_ERR(lpg->pbs_dev),
|
||||
"Failed to get PBS client device\n");
|
||||
} else if (sdam_count == 2) {
|
||||
/* Get the 2nd SDAM device for LUT pattern */
|
||||
lpg->lut_sdam = devm_nvmem_device_get(lpg->dev, "lut_sdam");
|
||||
if (IS_ERR(lpg->lut_sdam))
|
||||
return dev_err_probe(lpg->dev, PTR_ERR(lpg->lut_sdam),
|
||||
"Failed to get LPG LUT SDAM device\n");
|
||||
}
|
||||
|
||||
for (i = 0; i < lpg->num_channels; i++) {
|
||||
struct lpg_channel *chan = &lpg->channels[i];
|
||||
|
||||
if (chan->sdam_offset) {
|
||||
rc = nvmem_device_write(lpg->lpg_chan_sdam,
|
||||
SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET + chan->sdam_offset, 1, &val);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = lpg_sdam_configure_triggers(chan, 0);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = lpg_clear_pbs_trigger(chan->lpg, chan->lut_mask);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpg_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np;
|
||||
@@ -1346,6 +1626,10 @@ static int lpg_probe(struct platform_device *pdev)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = lpg_init_sdam(lpg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = lpg_init_lut(lpg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
@@ -1420,11 +1704,13 @@ static const struct lpg_data pm8994_lpg_data = {
|
||||
static const struct lpg_data pmi632_lpg_data = {
|
||||
.triled_base = 0xd000,
|
||||
|
||||
.lut_size = 64,
|
||||
|
||||
.num_channels = 5,
|
||||
.channels = (const struct lpg_channel_data[]) {
|
||||
{ .base = 0xb300, .triled_mask = BIT(7) },
|
||||
{ .base = 0xb400, .triled_mask = BIT(6) },
|
||||
{ .base = 0xb500, .triled_mask = BIT(5) },
|
||||
{ .base = 0xb300, .triled_mask = BIT(7), .sdam_offset = 0x48 },
|
||||
{ .base = 0xb400, .triled_mask = BIT(6), .sdam_offset = 0x56 },
|
||||
{ .base = 0xb500, .triled_mask = BIT(5), .sdam_offset = 0x64 },
|
||||
{ .base = 0xb600 },
|
||||
{ .base = 0xb700 },
|
||||
},
|
||||
@@ -1495,13 +1781,15 @@ static const struct lpg_data pm8150l_lpg_data = {
|
||||
};
|
||||
|
||||
static const struct lpg_data pm8350c_pwm_data = {
|
||||
.lut_size = 122,
|
||||
|
||||
.triled_base = 0xef00,
|
||||
|
||||
.num_channels = 4,
|
||||
.channels = (const struct lpg_channel_data[]) {
|
||||
{ .base = 0xe800, .triled_mask = BIT(7) },
|
||||
{ .base = 0xe900, .triled_mask = BIT(6) },
|
||||
{ .base = 0xea00, .triled_mask = BIT(5) },
|
||||
{ .base = 0xe800, .triled_mask = BIT(7), .sdam_offset = 0x48 },
|
||||
{ .base = 0xe900, .triled_mask = BIT(6), .sdam_offset = 0x56 },
|
||||
{ .base = 0xea00, .triled_mask = BIT(5), .sdam_offset = 0x64 },
|
||||
{ .base = 0xeb00 },
|
||||
},
|
||||
};
|
||||
|
Reference in New Issue
Block a user