Add samsung specific changes

This commit is contained in:
2025-08-11 14:29:00 +02:00
parent c66122e619
commit 4d134a1294
2688 changed files with 1127995 additions and 11475 deletions

View File

@@ -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"

View File

@@ -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
View 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, &reg_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");

View 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

File diff suppressed because it is too large Load Diff

View 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");

File diff suppressed because it is too large Load Diff

View 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");

View File

@@ -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 },
},
};