drivers/leds: Import Sony LED driver

*52.1.A.3.74

Signed-off-by: Sorayukii <sorayukii69@gmail.com>
This commit is contained in:
exer 2023-01-15 11:04:01 +00:00 • committed by Sorayukii
commit bbec06cc19
4 changed files with 1303 additions and 43 deletions

View file

@ -234,6 +234,7 @@ struct flash_node_data {
int current_ma;
int prev_current_ma;
u8 duration;
u32 duration_ms;
u8 id;
u8 ires_idx;
u8 default_ires_idx;
@ -349,6 +350,14 @@ static int max_ires_curr_ma_table[MAX_IRES_LEVELS] = {
FLASH_LED_IRES7P5_MAX_CURR_MA, FLASH_LED_IRES5P0_MAX_CURR_MA
};
static inline u8 get_safety_timer_code(u32 duration_ms)
{
if (!duration_ms)
return 0;
return (duration_ms / 10) - 1;
}
static inline int get_current_reg_code(int target_curr_ma, int ires_ua)
{
if (!ires_ua || !target_curr_ma || (target_curr_ma < (ires_ua / 1000)))
@ -1802,10 +1811,150 @@ static ssize_t qpnp_flash_led_max_current_show(struct device *dev,
return snprintf(buf, PAGE_SIZE, "%d\n", max_current);
}
/* sysfs show function for flash_led_fault_status */
static ssize_t qpnp_flash_led_fault_status_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct flash_switch_data *snode;
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct qpnp_flash_led *led;
int rc;
uint val;
snode = container_of(led_cdev, struct flash_switch_data, cdev);
led = dev_get_drvdata(&snode->pdev->dev);
rc = regmap_read(led->regmap,
FLASH_LED_REG_LED_STATUS1(led->base), &val);
if (rc) {
pr_err("Unable to read fault status rc(%d)\n", rc);
return rc;
}
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
/* sysfs store function for flash strobe */
static ssize_t qpnp_flash_led_strobe_type_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct flash_node_data *fnode;
unsigned long state;
struct led_classdev *led_cdev = dev_get_drvdata(dev);
ssize_t ret = -EINVAL;
ret = kstrtoul(buf, 10, &state);
if (ret)
return ret;
fnode = container_of(led_cdev, struct flash_node_data, cdev);
/* '0' for sw strobe; '1' for hw strobe */
if (state == 1)
fnode->strobe_ctrl |= FLASH_LED_HW_SW_STROBE_SEL_BIT;
else
fnode->strobe_ctrl &= ~FLASH_LED_HW_SW_STROBE_SEL_BIT;
return count;
}
/* sysfs show function for flash duration */
static ssize_t qpnp_flash_led_duration_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct qpnp_flash_led *led;
struct flash_node_data *flash_node;
struct led_classdev *led_cdev = dev_get_drvdata(dev);
flash_node = container_of(led_cdev, struct flash_node_data, cdev);
led = dev_get_drvdata(&flash_node->pdev->dev);
return scnprintf(buf, PAGE_SIZE, "%d\n", flash_node->duration_ms);
}
/* sysfs store function for flash duration */
static ssize_t qpnp_flash_led_duration_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct qpnp_flash_led *led;
struct flash_node_data *flash_node;
struct led_classdev *led_cdev = dev_get_drvdata(dev);
ssize_t ret;
unsigned long state;
flash_node = container_of(led_cdev, struct flash_node_data, cdev);
led = dev_get_drvdata(&flash_node->pdev->dev);
ret = kstrtoul(buf, 10, &state);
if (ret)
return ret;
flash_node->duration_ms = state;
flash_node->duration = (u8)(get_safety_timer_code(flash_node->duration_ms) |
FLASH_LED_SAFETY_TMR_ENABLE);
return count;
}
/* sysfs show function for flash ires_ua */
static ssize_t qpnp_flash_led_ires_ua_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct qpnp_flash_led *led;
struct flash_node_data *flash_node;
struct led_classdev *led_cdev = dev_get_drvdata(dev);
flash_node = container_of(led_cdev, struct flash_node_data, cdev);
led = dev_get_drvdata(&flash_node->pdev->dev);
return scnprintf(buf, PAGE_SIZE, "%d\n", flash_node->ires_ua);
}
/* sysfs store function for flash ires_ua */
static ssize_t qpnp_flash_led_ires_ua_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct qpnp_flash_led *led;
struct flash_node_data *flash_node;
struct led_classdev *led_cdev = dev_get_drvdata(dev);
ssize_t ret;
unsigned long state;
flash_node = container_of(led_cdev, struct flash_node_data, cdev);
led = dev_get_drvdata(&flash_node->pdev->dev);
ret = kstrtoul(buf, 10, &state);
if (ret)
return ret;
flash_node->default_ires_ua = flash_node->ires_ua = state;
flash_node->default_ires_idx = flash_node->ires_idx = FLASH_LED_IRES_BASE -
(flash_node->ires_ua - FLASH_LED_IRES_MIN_UA) / FLASH_LED_IRES_DIVISOR;
return count;
}
/* sysfs attributes exported by flash_led */
static struct device_attribute qpnp_flash_led_attrs[] = {
__ATTR(max_current, 0664, qpnp_flash_led_max_current_show, NULL),
__ATTR(enable, 0664, NULL, qpnp_flash_led_prepare_store),
__ATTR(fault_status, S_IRUSR | S_IRGRP,
qpnp_flash_led_fault_status_show,
NULL),
};
static struct device_attribute qpnp_flash_fnode_attrs[] = {
__ATTR(strobe, (S_IRUGO | S_IWUSR | S_IWGRP),
NULL,
qpnp_flash_led_strobe_type_store),
__ATTR(duration, (S_IRUGO | S_IWUSR | S_IWGRP),
qpnp_flash_led_duration_show,
qpnp_flash_led_duration_store),
__ATTR(ires_ua, (S_IRUGO | S_IWUSR | S_IWGRP),
qpnp_flash_led_ires_ua_show,
qpnp_flash_led_ires_ua_store),
};
/* irq handler */
@ -1876,14 +2025,6 @@ int qpnp_flash_led_unregister_irq_notifier(struct notifier_block *nb)
return atomic_notifier_chain_unregister(&irq_notifier_list, nb);
}
static inline u8 get_safety_timer_code(u32 duration_ms)
{
if (!duration_ms)
return 0;
return (duration_ms / 10) - 1;
}
static inline u8 get_vph_droop_thresh_code(u32 val_mv)
{
if (!val_mv)
@ -1989,6 +2130,7 @@ static int qpnp_flash_led_parse_each_led_dt(struct qpnp_flash_led *led,
fnode->duration = FLASH_LED_SAFETY_TMR_DISABLED;
rc = of_property_read_u32(node, "qcom,duration-ms", &val);
if (!rc) {
fnode->duration_ms = val;
fnode->duration = get_safety_timer_code(val);
if (fnode->duration)
fnode->duration |= FLASH_LED_SAFETY_TMR_ENABLE;
@ -2062,7 +2204,7 @@ static int qpnp_flash_led_parse_each_led_dt(struct qpnp_flash_led *led,
"qcom,hw-strobe-edge-trigger");
active_high = !of_property_read_bool(node,
"qcom,hw-strobe-active-low");
hw_strobe = 1;
hw_strobe = !of_property_read_bool(node, "somc,sw-strobe-init");
} else if (fnode->strobe_sel == LPG_STROBE) {
/* LPG strobe requires level trigger and active high */
edge_trigger = 0;
@ -2646,6 +2788,29 @@ static int qpnp_flash_led_parse_common_dt(struct qpnp_flash_led *led,
return 0;
}
static int qpnp_flash_led_init_strobe_settings(struct qpnp_flash_led *led)
{
int rc = 0, i, addr_offset;
rc = qpnp_flash_led_masked_write(led,
FLASH_LED_REG_STROBE_CFG(led->base),
FLASH_LED_STROBE_MASK,
led->pdata->hw_strobe_option);
if (rc < 0)
return rc;
for (i = 0; i < led->num_fnodes; i++) {
addr_offset = led->fnode[i].id;
rc = qpnp_flash_led_masked_write(led,
FLASH_LED_REG_STROBE_CTRL(led->base + addr_offset),
FLASH_LED_STROBE_MASK, led->fnode[i].strobe_ctrl);
if (rc < 0)
return rc;
}
return rc;
}
static int qpnp_flash_led_probe(struct platform_device *pdev)
{
struct qpnp_flash_led *led;
@ -2807,6 +2972,23 @@ static int qpnp_flash_led_probe(struct platform_device *pdev)
goto unreg_notifier;
}
rc = qpnp_flash_led_init_strobe_settings(led);
if (rc < 0) {
pr_err("Failed to initialize flash strobe, rc=%d\n", rc);
goto unreg_notifier;
}
for (i = 0; i < led->num_fnodes; i++) {
for (j = 0; j < ARRAY_SIZE(qpnp_flash_fnode_attrs); j++) {
rc = sysfs_create_file(&led->fnode[i].cdev.dev->kobj,
&qpnp_flash_fnode_attrs[j].attr);
if (rc < 0) {
pr_err("sysfs creation failed, rc=%d\n", rc);
goto sysfs_fnode_fail;
}
}
}
for (i = 0; i < led->num_snodes; i++) {
for (j = 0; j < ARRAY_SIZE(qpnp_flash_led_attrs); j++) {
rc = sysfs_create_file(&led->snode[i].cdev.dev->kobj,
@ -2824,6 +3006,16 @@ static int qpnp_flash_led_probe(struct platform_device *pdev)
return 0;
sysfs_fnode_fail:
for (--j; j >= 0; j--)
sysfs_remove_file(&led->fnode[i].cdev.dev->kobj,
&qpnp_flash_fnode_attrs[j].attr);
for (--i; i >= 0; i--) {
for (j = 0; j < ARRAY_SIZE(qpnp_flash_fnode_attrs); j++)
sysfs_remove_file(&led->fnode[i].cdev.dev->kobj,
&qpnp_flash_fnode_attrs[j].attr);
}
sysfs_fail:
for (--j; j >= 0; j--)
sysfs_remove_file(&led->snode[i].cdev.dev->kobj,
@ -2868,6 +3060,13 @@ static int qpnp_flash_led_remove(struct platform_device *pdev)
led_classdev_unregister(&led->snode[--i].cdev);
i = led->num_fnodes;
for (i = 0; i < led->num_fnodes; i++) {
for (j = 0; j < ARRAY_SIZE(qpnp_flash_fnode_attrs); j++)
sysfs_remove_file(&led->fnode[i].cdev.dev->kobj,
&qpnp_flash_fnode_attrs[j].attr);
}
while (i > 0)
led_classdev_unregister(&led->fnode[--i].cdev);

View file

@ -168,7 +168,7 @@
/* Other definitions */
#define HAP_BRAKE_PAT_LEN 4
#define HAP_WAVE_SAMP_LEN 8
#define NUM_WF_SET 4
#define NUM_WF_SET 10
#define HAP_WAVE_SAMP_SET_LEN (HAP_WAVE_SAMP_LEN * NUM_WF_SET)
#define HAP_RATE_CFG_STEP_US 5
#define HAP_WAVE_PLAY_RATE_US_MIN 0
@ -176,6 +176,10 @@
#define HAP_WAVE_PLAY_RATE_US_MAX 20475
#define HAP_MAX_PLAY_TIME_MS 15000
/* Auto resonance error retry count */
#define HAP_AUTO_RES_MAX_CNT_AUDIO 6
#define HAP_AUTO_RES_ERR_RETRY_MAX_CNT_AUDIO 500
enum hap_brake_pat {
NO_BRAKE = 0,
BRAKE_VMAX_4,
@ -358,6 +362,9 @@ struct hap_chip {
bool play_irq_en;
bool auto_res_err_recovery_hw;
bool vcc_pon_enabled;
bool auto_res_err_audio;
u8 auto_res_cnt_audio;
u16 auto_res_err_retry_cnt_audio;
};
static int qpnp_haptics_parse_buffer_dt(struct hap_chip *chip);
@ -596,16 +603,22 @@ static void qpnp_haptics_update_lra_frequency(struct hap_chip *chip)
if ((val & AUTO_RES_ERROR_BIT) ||
((play_rate_code <= chip->drive_period_code_min_limit) ||
(play_rate_code >= chip->drive_period_code_max_limit))) {
if (val & AUTO_RES_ERROR_BIT)
if (val & AUTO_RES_ERROR_BIT) {
pr_debug("Auto-resonance error %x\n", val);
else
chip->auto_res_cnt_audio = 0;
} else
pr_debug("play rate %x out of bounds [min: 0x%x, max: 0x%x]\n",
play_rate_code,
chip->drive_period_code_min_limit,
chip->drive_period_code_max_limit);
rc = qpnp_haptics_auto_res_enable(chip, false);
if (rc < 0)
pr_debug("Auto-resonance disable failed\n");
if (chip->play_mode == HAP_AUDIO) {
chip->auto_res_err_audio = true;
} else {
rc = qpnp_haptics_auto_res_enable(chip, false);
if (rc < 0)
pr_debug("Auto-resonance disable failed\n");
}
return;
}
@ -755,10 +768,11 @@ static int qpnp_haptics_play(struct hap_chip *chip, bool enable)
if (chip->play_mode == HAP_BUFFER)
time_ms = get_buffer_mode_duration(chip);
hrtimer_start(&chip->stop_timer,
ktime_set(time_ms / MSEC_PER_SEC,
(time_ms % MSEC_PER_SEC) * NSEC_PER_MSEC),
HRTIMER_MODE_REL);
if (chip->play_mode != HAP_AUDIO)
hrtimer_start(&chip->stop_timer,
ktime_set(time_ms / MSEC_PER_SEC,
(time_ms % MSEC_PER_SEC) * NSEC_PER_MSEC),
HRTIMER_MODE_REL);
rc = qpnp_haptics_auto_res_enable(chip, true);
if (rc < 0) {
@ -766,6 +780,12 @@ static int qpnp_haptics_play(struct hap_chip *chip, bool enable)
goto out;
}
if (chip->play_mode == HAP_AUDIO) {
chip->auto_res_err_audio = false;
chip->auto_res_cnt_audio = 0;
chip->auto_res_err_retry_cnt_audio = 0;
}
if (is_sw_lra_auto_resonance_control(chip))
hrtimer_start(&chip->auto_res_err_poll_timer,
ktime_set(0, AUTO_RES_ERR_POLL_TIME_NS),
@ -844,6 +864,33 @@ static enum hrtimer_restart hap_auto_res_err_poll_timer(struct hrtimer *timer)
{
struct hap_chip *chip = container_of(timer, struct hap_chip,
auto_res_err_poll_timer);
int rc = 0;
u8 val = 0;
if (chip->play_mode == HAP_AUDIO) {
if (chip->auto_res_err_audio) {
val = 0;
rc = qpnp_haptics_write_reg(chip, HAP_EN_CTL_REG(chip), &val, 1);
if (rc < 0)
pr_debug("Error in disabling module forcibly\n");
val = HAP_EN_BIT;
rc = qpnp_haptics_write_reg(chip, HAP_EN_CTL_REG(chip), &val, 1);
if (rc < 0)
pr_debug("Error in enbling module forcibly\n");
chip->auto_res_err_audio = false;
chip->auto_res_err_retry_cnt_audio++;
} else {
chip->auto_res_cnt_audio++;
}
qpnp_haptics_update_lra_frequency(chip);
if ((HAP_AUTO_RES_MAX_CNT_AUDIO > chip->auto_res_cnt_audio)
&& (HAP_AUTO_RES_ERR_RETRY_MAX_CNT_AUDIO > chip->auto_res_err_retry_cnt_audio)) {
hrtimer_forward(&chip->auto_res_err_poll_timer, ktime_get(),
ktime_set(0, AUTO_RES_ERR_POLL_TIME_NS));
return HRTIMER_RESTART;
} else {
return HRTIMER_NORESTART;
}
}
if (!(chip->status_flags & AUTO_RESONANCE_ENABLED))
return HRTIMER_NORESTART;
@ -1335,24 +1382,25 @@ static irqreturn_t qpnp_haptics_play_irq_handler(int irq, void *data)
struct hap_chip *chip = data;
int rc;
if (chip->module_en == 0)
goto irq_handled;
if (chip->play_mode != HAP_BUFFER)
goto irq_handled;
if (chip->wave_samp[chip->wave_samp_idx + HAP_WAVE_SAMP_LEN] > 0) {
chip->wave_samp_idx += HAP_WAVE_SAMP_LEN;
if (chip->wave_samp_idx >= ARRAY_SIZE(chip->wave_samp)) {
pr_debug("Samples over\n");
} else {
pr_debug("moving to next sample set %d\n",
chip->wave_samp_idx);
chip->wave_samp_idx += HAP_WAVE_SAMP_LEN;
if (chip->wave_samp_idx >= ARRAY_SIZE(chip->wave_samp)) {
pr_debug("Samples over\n");
} else {
pr_debug("moving to next sample set %d\n",
chip->wave_samp_idx);
/* Moving to next set of wave sample */
rc = qpnp_haptics_buffer_config(chip, NULL, false);
if (rc < 0) {
pr_err("Error in configuring buffer, rc=%d\n",
rc);
goto irq_handled;
}
/* Moving to next set of wave sample */
rc = qpnp_haptics_buffer_config(chip, NULL, false);
if (rc < 0) {
pr_err("Error in configuring buffer, rc=%d\n",
rc);
goto irq_handled;
}
}
@ -1529,6 +1577,8 @@ static ssize_t qpnp_haptics_store_activate(struct device *dev,
atomic_set(&chip->state, 1);
schedule_work(&chip->haptics_work);
} else {
if (is_sw_lra_auto_resonance_control(chip))
hrtimer_cancel(&chip->auto_res_err_poll_timer);
rc = qpnp_haptics_mod_enable(chip, false);
if (rc < 0) {
pr_err("Error in disabling module, rc=%d\n", rc);
@ -1567,6 +1617,7 @@ static ssize_t qpnp_haptics_store_play_mode(struct device *dev,
struct hap_chip *chip = container_of(cdev, struct hap_chip, cdev);
char str[HAP_STR_SIZE + 1];
int rc = 0, temp, old_mode;
u8 val = 0;
rc = parse_string(buf, str);
if (rc < 0)
@ -1583,6 +1634,8 @@ static ssize_t qpnp_haptics_store_play_mode(struct device *dev,
else
return -EINVAL;
rc = qpnp_haptics_write_reg(chip, HAP_BRAKE_REG(chip), &val, 1);
if (temp == chip->play_mode)
return count;
@ -1598,7 +1651,7 @@ static ssize_t qpnp_haptics_store_play_mode(struct device *dev,
}
}
rc = qpnp_haptics_buffer_config(chip, NULL, true);
rc = qpnp_haptics_buffer_config(chip, NULL, false);
} else if (temp == HAP_PWM) {
rc = qpnp_haptics_parse_pwm_dt(chip);
if (!rc)
@ -1669,6 +1722,8 @@ static ssize_t qpnp_haptics_store_wf_samp(struct device *dev,
for (i = 0; i < ARRAY_SIZE(chip->wave_samp); i++)
chip->wave_samp[i] = samp[i];
chip->wave_samp_idx = 0;
rc = qpnp_haptics_buffer_config(chip, NULL, false);
if (rc < 0) {
pr_err("Error in configuring buffer mode %d\n", rc);
@ -1971,7 +2026,10 @@ static int qpnp_haptics_parse_buffer_dt(struct hap_chip *chip)
{
struct device_node *node = chip->pdev->dev.of_node;
u32 temp;
/*
int rc, i, wf_samp_len;
*/
int rc, wf_samp_len;
if (chip->wave_rep_cnt > 0 || chip->wave_s_rep_cnt > 0)
return 0;
@ -2012,8 +2070,10 @@ static int qpnp_haptics_parse_buffer_dt(struct hap_chip *chip)
}
} else {
/* Use default values */
/*
for (i = 0; i < HAP_WAVE_SAMP_LEN; i++)
chip->wave_samp[i] = HAP_WF_SAMP_MAX;
*/
wf_samp_len = HAP_WAVE_SAMP_LEN;
}

View file

@ -238,6 +238,13 @@
#define QPNP_WLED_AVDD_MV_TO_REG(val) \
((val - QPNP_WLED_AVDD_MIN_MV) / QPNP_WLED_AVDD_STEP_MV)
#define QPNP_WLED_CURR_SCALE_MAX 100
#define QPNP_WLED_BL_SCALE_MAX 1000
#define QPNP_WLED_BUFF_SIZE 128
#define BR_MAX_FIGURE 9
#define AREA_COUNT_MAX 9999999
/* output feedback mode */
enum qpnp_wled_fdbk_op {
QPNP_WLED_FDBK_AUTO,
@ -312,6 +319,12 @@ static struct wled_vref_setting vref_setting_pmi8998 = {
60000, 397500, 22500, 127500,
};
static unsigned long *area_count;
static int *area_count_table;
static int area_count_table_size;
static int now_area;
static unsigned long start_jiffies;
/**
* qpnp_wled - wed data structure
* @ cdev - led class device
@ -426,6 +439,11 @@ struct qpnp_wled {
bool auto_calib_done;
bool module_dis_perm;
ktime_t start_ovp_fault_time;
int init_br_ua;
bool bl_scale_enabled;
int bl_scale;
bool calc_curr;
int curr_scale;
};
static int qpnp_wled_step_delay_us = 52000;
@ -576,10 +594,20 @@ static int qpnp_wled_set_level(struct qpnp_wled *wled, int level)
u8 reg;
u16 low_limit = WLED_MAX_LEVEL_4095 * 4 / 1000;
if (wled->calc_curr &&
wled->curr_scale != QPNP_WLED_CURR_SCALE_MAX)
level = level * wled->curr_scale / QPNP_WLED_CURR_SCALE_MAX;
if (wled->bl_scale_enabled && wled->bl_scale > 0 &&
wled->bl_scale < QPNP_WLED_BL_SCALE_MAX)
level = level * wled->bl_scale / QPNP_WLED_BL_SCALE_MAX;
/* WLED's lower limit of operation is 0.4% */
if (level > 0 && level < low_limit)
level = low_limit;
pr_debug("%s: brightness=%d level=%d\n",
__func__, wled->cdev.brightness, level);
/* set brightness registers */
for (i = 0; i < wled->max_strings; i++) {
reg = level & QPNP_WLED_BRIGHT_LSB_MASK;
@ -1067,7 +1095,11 @@ static ssize_t qpnp_wled_fs_curr_ua_store(struct device *dev,
else if (data > QPNP_WLED_FS_CURR_MAX_UA)
data = QPNP_WLED_FS_CURR_MAX_UA;
reg = data / QPNP_WLED_FS_CURR_STEP_UA;
if (wled->calc_curr)
reg = (data + (QPNP_WLED_FS_CURR_STEP_UA - 1)) /
QPNP_WLED_FS_CURR_STEP_UA;
else
reg = data / QPNP_WLED_FS_CURR_STEP_UA;
rc = qpnp_wled_masked_write_reg(wled,
QPNP_WLED_FS_CURR_REG(wled->sink_base, i),
QPNP_WLED_FS_CURR_MASK, reg);
@ -1086,6 +1118,148 @@ static ssize_t qpnp_wled_fs_curr_ua_store(struct device *dev,
return count;
}
static ssize_t qpnp_wled_bl_scale_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct qpnp_wled *wled = dev_get_drvdata(dev);
if (!wled->bl_scale_enabled)
wled->bl_scale = QPNP_WLED_BL_SCALE_MAX;
dev_dbg(&wled->pdev->dev, "%s: bl_scale = %d\n", __func__, wled->bl_scale);
return snprintf(buf, QPNP_WLED_BUFF_SIZE, "%u\n", wled->bl_scale);
}
static ssize_t qpnp_wled_bl_scale_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct qpnp_wled *wled = dev_get_drvdata(dev);
unsigned long scale = 0;
ssize_t ret = -EINVAL;
if (!wled->bl_scale_enabled) {
wled->bl_scale = QPNP_WLED_BL_SCALE_MAX;
dev_err(&wled->pdev->dev, "Sysfs bl_scale is not enabled\n");
goto exit;
}
ret = kstrtoul(buf, 10, &scale);
if (!ret) {
ret = size;
if (scale > QPNP_WLED_BL_SCALE_MAX)
scale = QPNP_WLED_BL_SCALE_MAX;
wled->bl_scale = scale;
dev_dbg(&wled->pdev->dev, "%s: bl_scale = %d\n", __func__, wled->bl_scale);
} else {
dev_err(&wled->pdev->dev, "Failure to set sysfs bl_scale\n");
ret = -EINVAL;
}
exit:
return ret;
}
static void update_areacount(int new_area)
{
unsigned long now;
unsigned long duration;
now = jiffies;
duration = (now - start_jiffies) >= 0 ?
(now - start_jiffies) : (now + start_jiffies);
area_count[now_area] = area_count[now_area]
+ jiffies_to_msecs(duration);
now_area = new_area;
start_jiffies = now;
}
static ssize_t qpnp_wled_area_count_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct qpnp_wled *wled = dev_get_drvdata(dev);
int i;
char area_count_str[QPNP_WLED_BUFF_SIZE];
char count_data[BR_MAX_FIGURE];
if (!area_count_table_size)
return snprintf(buf, QPNP_WLED_BUFF_SIZE, "area_count is not supported\n");
mutex_lock(&wled->cdev.led_access);
/* fixed statistics */
update_areacount(now_area);
memset(area_count_str, 0, sizeof(char) * QPNP_WLED_BUFF_SIZE);
for (i = 0; i < area_count_table_size; i++) {
dev_dbg(&wled->pdev->dev, "%ld, ", area_count[i]);
memset(count_data, 0, sizeof(char) * BR_MAX_FIGURE);
if (area_count[i] > AREA_COUNT_MAX) {
/* over 167 min */
area_count[i] = AREA_COUNT_MAX;
}
if (i == 0) {
snprintf(count_data, BR_MAX_FIGURE,
"%ld", area_count[i]);
strlcpy(area_count_str, count_data, QPNP_WLED_BUFF_SIZE);
} else {
snprintf(count_data, BR_MAX_FIGURE,
",%ld", area_count[i]);
strlcat(area_count_str, count_data, QPNP_WLED_BUFF_SIZE);
}
}
memset(area_count, 0, sizeof(unsigned long) * area_count_table_size);
mutex_unlock(&wled->cdev.led_access);
return snprintf(buf, QPNP_WLED_BUFF_SIZE, "%s\n", area_count_str);
}
static ssize_t qpnp_wled_en_cabc_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct qpnp_wled *wled = dev_get_drvdata(dev);
char *str = wled->en_cabc ? "true" : "false";
dev_dbg(&wled->pdev->dev, "%s: en_cabc:%s\n", __func__, str);
return snprintf(buf, QPNP_WLED_BUFF_SIZE, "%s\n", str);
}
static ssize_t qpnp_wled_en_cabc_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct qpnp_wled *wled = dev_get_drvdata(dev);
int data, i, rc;
u8 reg, mask;
rc = kstrtoint(buf, 10, &data);
if (rc)
return rc;
if (data)
wled->en_cabc = true;
else
wled->en_cabc = false;
pr_info("%s: set wled->en_cabc:%d\n", __func__, wled->en_cabc);
for (i = 0; i < wled->num_strings; i++) {
reg = wled->en_cabc ? (1 << QPNP_WLED_CABC_SHIFT) : 0;
mask = QPNP_WLED_CABC_MASK;
rc = qpnp_wled_masked_write_reg(wled,
QPNP_WLED_CABC_REG(wled->sink_base, i),
mask, reg);
if (rc < 0)
return rc;
}
rc = qpnp_wled_sync_reg_toggle(wled);
if (rc < 0) {
dev_err(&wled->pdev->dev, "Failed to toggle sync reg %d\n", rc);
return rc;
}
return size;
}
/* sysfs attributes exported by wled */
static struct device_attribute qpnp_wled_attrs[] = {
__ATTR(dump_regs, 0664, qpnp_wled_dump_regs_show, NULL),
@ -1098,8 +1272,42 @@ static struct device_attribute qpnp_wled_attrs[] = {
__ATTR(ramp_step, 0664, qpnp_wled_ramp_step_show,
qpnp_wled_ramp_step_store),
__ATTR(secure_mode, 0664, NULL, qpnp_wled_irq_control),
__ATTR(bl_scale, (S_IRUGO | S_IWUSR | S_IWGRP),
qpnp_wled_bl_scale_show,
qpnp_wled_bl_scale_store),
__ATTR(area_count, (S_IRUSR | S_IRGRP),
qpnp_wled_area_count_show,
NULL),
__ATTR(en_cabc, (S_IRUGO | S_IWUSR | S_IWGRP),
qpnp_wled_en_cabc_show,
qpnp_wled_en_cabc_store),
};
static int get_area(int level)
{
int i;
for (i = 0; i < area_count_table_size; i++) {
if (i == 0) {
if (level == area_count_table[i])
break;
} else {
if (level <= area_count_table[i])
break;
}
}
return i;
}
static void qpnp_wled_update_area(int level)
{
int new_area = get_area(level);
if (now_area != new_area)
update_areacount(new_area);
}
/* worker for setting wled brightness */
static void qpnp_wled_work(struct work_struct *work)
{
@ -1116,6 +1324,7 @@ static void qpnp_wled_work(struct work_struct *work)
}
level = wled->cdev.brightness;
qpnp_wled_update_area(level);
if (wled->brt_map_table) {
/*
@ -1164,6 +1373,7 @@ static void qpnp_wled_work(struct work_struct *work)
level ? "en" : "dis");
goto unlock_mutex;
}
usleep_range(100, 101);
}
wled->prev_state = !!level;
@ -2146,8 +2356,11 @@ static int qpnp_wled_config(struct qpnp_wled *wled)
/* FULL SCALE CURRENT */
if (wled->fs_curr_ua > QPNP_WLED_FS_CURR_MAX_UA)
wled->fs_curr_ua = QPNP_WLED_FS_CURR_MAX_UA;
reg = wled->fs_curr_ua / QPNP_WLED_FS_CURR_STEP_UA;
if (wled->calc_curr)
reg = (wled->fs_curr_ua + (QPNP_WLED_FS_CURR_STEP_UA - 1)) /
QPNP_WLED_FS_CURR_STEP_UA;
else
reg = wled->fs_curr_ua / QPNP_WLED_FS_CURR_STEP_UA;
mask = QPNP_WLED_FS_CURR_MASK;
rc = qpnp_wled_masked_write_reg(wled,
QPNP_WLED_FS_CURR_REG(wled->sink_base, i),
@ -2304,6 +2517,39 @@ static int qpnp_wled_config(struct qpnp_wled *wled)
return 0;
}
static void qpnp_wled_set_init_br(struct qpnp_wled *wled)
{
int calc_init_br;
if (wled->fs_curr_ua) {
calc_init_br = (wled->cdev.max_brightness * wled->init_br_ua)
/ wled->fs_curr_ua;
} else {
calc_init_br = (wled->cdev.max_brightness * wled->init_br_ua)
/ QPNP_WLED_FS_CURR_MAX_UA;
}
qpnp_wled_set(&wled->cdev, calc_init_br);
}
static int qpnp_wled_set_scale(struct qpnp_wled *wled)
{
u8 reg = 0;
int rc, curr;
wled->curr_scale = QPNP_WLED_CURR_SCALE_MAX;
rc = qpnp_wled_read_reg(wled, QPNP_WLED_FS_CURR_REG(wled->sink_base,
wled->strings[0]), &reg);
if (rc)
return rc;
reg &= QPNP_WLED_FS_CURR_MASK;
curr = reg * QPNP_WLED_FS_CURR_STEP_UA;
if (curr > wled->fs_curr_ua)
wled->curr_scale = (wled->fs_curr_ua *
QPNP_WLED_CURR_SCALE_MAX) / curr;
dev_dbg(&wled->pdev->dev, "%s: curr = %d fs_curr_ua = %d curr_scale = %d\n",
__func__, curr, wled->fs_curr_ua, wled->curr_scale);
return 0;
}
/* parse wled dtsi parameters */
static int qpnp_wled_parse_dt(struct qpnp_wled *wled)
{
@ -2634,6 +2880,10 @@ static int qpnp_wled_parse_dt(struct qpnp_wled *wled)
if (!rc)
wled->cons_sync_write_delay_us = temp_val;
wled->calc_curr = of_property_read_bool(pdev->dev.of_node,
"somc,calc-curr");
wled->bl_scale_enabled = of_property_read_bool(pdev->dev.of_node,
"somc,bl-scale-enabled");
wled->en_9b_dim_res = of_property_read_bool(pdev->dev.of_node,
"qcom,en-9b-dim-res");
wled->en_phase_stag = of_property_read_bool(pdev->dev.of_node,
@ -2677,6 +2927,51 @@ static int qpnp_wled_parse_dt(struct qpnp_wled *wled)
wled->auto_calib_enabled = of_property_read_bool(pdev->dev.of_node,
"qcom,auto-calibration-enable");
wled->init_br_ua = LED_OFF;
rc = of_property_read_u32(pdev->dev.of_node,
"somc,init-br-ua", &temp_val);
if (!rc) {
wled->init_br_ua = temp_val;
} else if (rc != -EINVAL) {
dev_err(&pdev->dev, "Unable to read init ua\n");
return rc;
}
rc = of_property_read_u32(pdev->dev.of_node,
"somc,area_count_table_size", &temp_val);
if (!rc) {
area_count_table_size = temp_val;
} else if (rc != -EINVAL) {
dev_err(&pdev->dev, "Unable to read area_count_table_size\n");
return rc;
}
if (area_count_table_size > 0) {
area_count_table = devm_kzalloc(&pdev->dev,
sizeof(int) * area_count_table_size, GFP_KERNEL);
if (!area_count_table)
return -ENOMEM;
area_count = devm_kzalloc(&pdev->dev,
sizeof(unsigned long) * area_count_table_size, GFP_KERNEL);
if (!area_count_table)
return -ENOMEM;
rc = of_property_read_u32_array(pdev->dev.of_node, "somc,area_count_table",
area_count_table, area_count_table_size);
if (rc < 0) {
dev_err(&pdev->dev, "Unable to read somc,area_count_table\n");
temp_val = (WLED_MAX_LEVEL_4095 % (area_count_table_size - 1) > 0 ?
((WLED_MAX_LEVEL_4095 / (area_count_table_size - 1)) + 1) :
(WLED_MAX_LEVEL_4095 / (area_count_table_size - 1)));
area_count_table[0] = 0;
for (i = 1; i < area_count_table_size; i++) {
area_count_table[i] = temp_val * i;
}
}
}
return 0;
}
@ -2758,6 +3053,13 @@ static int qpnp_wled_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "wled config failed\n");
return rc;
}
if (wled->calc_curr) {
rc = qpnp_wled_set_scale(wled);
if (rc) {
dev_err(&pdev->dev, "wled config failed\n");
return rc;
}
}
INIT_WORK(&wled->work, qpnp_wled_work);
wled->ramp_ms = QPNP_WLED_RAMP_DLY_MS;
@ -2767,6 +3069,8 @@ static int qpnp_wled_probe(struct platform_device *pdev)
wled->cdev.brightness_get = qpnp_wled_get;
wled->cdev.max_brightness = WLED_MAX_LEVEL_4095;
now_area = 0;
start_jiffies = jiffies;
rc = led_classdev_register(&pdev->dev, &wled->cdev);
if (rc) {
@ -2783,6 +3087,9 @@ static int qpnp_wled_probe(struct platform_device *pdev)
}
}
if (wled->init_br_ua)
qpnp_wled_set_init_br(wled);
return 0;
sysfs_fail:
@ -2810,6 +3117,8 @@ static int qpnp_wled_remove(struct platform_device *pdev)
cancel_work_sync(&wled->work);
destroy_workqueue(wled->wq);
mutex_destroy(&wled->lock);
devm_kfree(&pdev->dev, area_count_table);
devm_kfree(&pdev->dev, area_count);
return 0;
}

View file

@ -175,7 +175,7 @@
#define RGB_LED_EN_CTL(base) (base + 0x46)
#define RGB_LED_ATC_CTL(base) (base + 0x47)
#define RGB_MAX_LEVEL LED_FULL
#define RGB_MAX_LEVEL 512
#define RGB_LED_ENABLE_RED 0x80
#define RGB_LED_ENABLE_GREEN 0x40
#define RGB_LED_ENABLE_BLUE 0x20
@ -187,6 +187,7 @@
#define PWM_LUT_MAX_SIZE 63
#define PWM_GPLED_LUT_MAX_SIZE 31
#define RGB_LED_DISABLE 0x00
#define PWM_US 1000
#define MPP_MAX_LEVEL LED_FULL
#define LED_MPP_MODE_CTRL(base) (base + 0x40)
@ -251,6 +252,13 @@
#define NUM_KPDBL_LEDS 4
#define KPDBL_MASTER_BIT_INDEX 0
#define RGB_LED_MAX_PAUSE_REG 254
#define RGB_LED_MAX_PAUSE_TIME 130048
#define RGB_LED_LUT_MAX_PAUSE 255
#define RGB_LUT_INDEX_OFFSET 1
#define RGB_CURR_DEFAULT_PATTERN 1
#define RGB_CURR_UNIT_NUM 3
/**
* enum qpnp_leds - QPNP supported led ids
* @QPNP_ID_WLED - White led backlight
@ -306,6 +314,8 @@ enum led_mode {
PWM_MODE = 0,
LPG_MODE,
MANUAL_MODE,
PWM_DIRECT_MODE,
LPG_SYNC_MODE,
};
static u8 wled_debug_regs[] = {
@ -353,6 +363,7 @@ static u8 gpio_debug_regs[] = {
* @default_mode - default mode of LED as set in device tree
* @use_blink - use blink sysfs entry
* @blinking - device is currently blinking w/LPG mode
* @pwm_channel - pwm channel to be configured for led
*/
struct pwm_config_data {
struct lut_params lut_params;
@ -365,6 +376,7 @@ struct pwm_config_data {
bool pwm_enabled;
bool use_blink;
bool blinking;
int pwm_channel;
};
/**
@ -499,10 +511,33 @@ struct kpdbl_config_data {
* rgb_config_data - rgb configuration data
* @pwm_cfg - device pwm configuration
* @enable - bits to enable led
* @single_pwm_value - single color max value
* @mix_pwm_value - max color max value
*/
struct rgb_config_data {
struct pwm_config_data *pwm_cfg;
u8 enable;
u32 single_pwm_value;
u32 mix_pwm_value;
};
struct qpnp_led_data;
enum rgb_sync_state {
RGBSYNC_STATE_NOT_BLINK,
RGBSYNC_STATE_CONFIGURE_TO_BLINK,
RGBSYNC_STATE_BLINKING,
};
#define LPG_CHANNEL_MAX 6
/**
* struct rgb_sync - rgb led synchrnize structure
* @config - pointer to rgb config data
*/
struct rgb_sync {
struct led_classdev cdev;
struct platform_device *pdev;
enum rgb_sync_state sync_state;
struct qpnp_led_data *led_data[LPG_CHANNEL_MAX];
};
/**
@ -552,6 +587,7 @@ struct qpnp_led_data {
struct rgb_config_data *rgb_cfg;
struct mpp_config_data *mpp_cfg;
struct gpio_config_data *gpio_cfg;
struct rgb_sync *rgb_sync;
int max_current;
bool default_on;
bool in_order_command_processing;
@ -564,6 +600,19 @@ static u32 kpdbl_master_period_us;
DECLARE_BITMAP(kpdbl_leds_in_use, NUM_KPDBL_LEDS);
static bool is_kpdbl_master_turn_on;
static int rgb_current_index;
static int __init rgb_current_setup(char *str)
{
unsigned int res;
if (!*str)
return 0;
if (!kstrtouint(str, 16, &res))
rgb_current_index = res;
return 1;
}
__setup("oemandroidboot.babe137e=", rgb_current_setup);
static int
qpnp_led_masked_write(struct qpnp_led_data *led, u16 addr, u8 mask, u8 val)
{
@ -1708,11 +1757,75 @@ static int qpnp_kpdbl_set(struct qpnp_led_data *led)
return 0;
}
static int qpnp_rgb_set_direct(struct qpnp_led_data *led)
{
struct pwm_period_config pwm_conf = {
.pwm_size = PM_PWM_SIZE_9BIT,
.clk = PM_PWM_CLK_19P2MHZ,
.pre_div = PM_PWM_PDIV_5,
.pre_div_exp = 3,
};
int rc;
int duty_ns, period_us;
if (led->rgb_cfg->pwm_cfg->mode == LPG_SYNC_MODE) {
dev_err(&led->pdev->dev, "%s wrong mode\n", __func__);
return -EFAULT;
}
if (led->cdev.brightness) {
period_us = PWM_US;
duty_ns = ((period_us * NSEC_PER_USEC) /
(RGB_MAX_LEVEL-1)) * led->cdev.brightness;
rc = pwm_config(led->rgb_cfg->pwm_cfg->pwm_dev, duty_ns,
period_us * NSEC_PER_USEC);
rc = pwm_config_period_value(led->rgb_cfg->pwm_cfg->pwm_dev,
&pwm_conf, led->cdev.brightness);
if (rc < 0) {
dev_err(&led->pdev->dev,
"pwm config failed\n");
return rc;
}
rc = qpnp_led_masked_write(led,
RGB_LED_EN_CTL(led->base),
led->rgb_cfg->enable, led->rgb_cfg->enable);
if (rc) {
dev_err(&led->pdev->dev,
"Failed to write led enable reg\n");
return rc;
}
if (led->rgb_cfg->pwm_cfg->pwm_enabled) {
pwm_disable(led->rgb_cfg->pwm_cfg->pwm_dev);
led->rgb_cfg->pwm_cfg->pwm_enabled = 0;
}
rc = pwm_enable(led->rgb_cfg->pwm_cfg->pwm_dev);
if (!rc)
led->rgb_cfg->pwm_cfg->pwm_enabled = 1;
} else {
pwm_disable(led->rgb_cfg->pwm_cfg->pwm_dev);
led->rgb_cfg->pwm_cfg->pwm_enabled = 0;
rc = qpnp_led_masked_write(led,
RGB_LED_EN_CTL(led->base),
led->rgb_cfg->enable, RGB_LED_DISABLE);
if (rc) {
dev_err(&led->pdev->dev,
"Failed to write led enable reg\n");
return rc;
}
}
return 0;
}
static int qpnp_rgb_set(struct qpnp_led_data *led)
{
int rc;
int duty_us, duty_ns, period_us;
if ((led->rgb_cfg->pwm_cfg->mode == PWM_DIRECT_MODE)
|| (led->rgb_cfg->pwm_cfg->mode == LPG_SYNC_MODE))
return qpnp_rgb_set_direct(led);
if (led->cdev.brightness) {
if (!led->rgb_cfg->pwm_cfg->blinking)
led->rgb_cfg->pwm_cfg->mode =
@ -1887,7 +2000,7 @@ static int qpnp_led_set_max_brightness(struct qpnp_led_data *led)
case QPNP_ID_RGB_RED:
case QPNP_ID_RGB_GREEN:
case QPNP_ID_RGB_BLUE:
led->cdev.max_brightness = RGB_MAX_LEVEL;
led->cdev.max_brightness = led->max_current;
break;
case QPNP_ID_LED_MPP:
if (led->mpp_cfg->pwm_mode == MANUAL_MODE)
@ -2958,9 +3071,456 @@ static int qpnp_kpdbl_init(struct qpnp_led_data *led)
return 0;
}
static void qpnp_rgb_set_duration(struct qpnp_led_data *led)
{
unsigned long long_pause, duration;
const unsigned max_pause = RGB_LED_MAX_PAUSE_REG;
const unsigned max_pause_time = RGB_LED_MAX_PAUSE_TIME;
if (led->rgb_cfg->pwm_cfg->lut_params.lut_pause_hi > max_pause_time)
led->rgb_cfg->pwm_cfg->lut_params.lut_pause_hi
= max_pause_time;
if (led->rgb_cfg->pwm_cfg->lut_params.lut_pause_lo > max_pause_time)
led->rgb_cfg->pwm_cfg->lut_params.lut_pause_lo
= max_pause_time;
long_pause = ((led->rgb_cfg->pwm_cfg->lut_params.lut_pause_hi
>= led->rgb_cfg->pwm_cfg->lut_params.lut_pause_lo) ?
led->rgb_cfg->pwm_cfg->lut_params.lut_pause_hi :
led->rgb_cfg->pwm_cfg->lut_params.lut_pause_lo);
duration = (long_pause + (max_pause - 1)) / max_pause;
led->rgb_cfg->pwm_cfg->lut_params.lut_pause_hi
= (led->rgb_cfg->pwm_cfg->lut_params.lut_pause_hi / duration) + 1;
led->rgb_cfg->pwm_cfg->lut_params.lut_pause_lo
= (led->rgb_cfg->pwm_cfg->lut_params.lut_pause_lo / duration) + 1;
led->rgb_cfg->pwm_cfg->lut_params.ramp_step_ms = (duration - 1);
led->rgb_cfg->pwm_cfg->lut_params.use_duration = false;
pr_debug("%s : pause_hi=%d pause_lo=%d step_ms=%d\n", __func__,
led->rgb_cfg->pwm_cfg->lut_params.lut_pause_hi,
led->rgb_cfg->pwm_cfg->lut_params.lut_pause_lo,
led->rgb_cfg->pwm_cfg->lut_params.ramp_step_ms);
return;
}
static int rgbled_set_lut_to_register(struct qpnp_led_data *led)
{
int rc;
int i;
/* set PWM period to 1.06666[ms] (2^9 {9bit} * 5 * 2^3 / 19.2 [us]) */
struct pwm_period_config pwm_config = {
.pwm_size = PM_PWM_SIZE_9BIT,
.clk = PM_PWM_CLK_19P2MHZ,
.pre_div = PM_PWM_PDIV_5,
.pre_div_exp = 3,
};
struct lut_config lut_conf = {
.hi_index = led->rgb_cfg->pwm_cfg->duty_cycles->start_idx
+ led->rgb_cfg->pwm_cfg->lut_params.idx_len - 1,
.lo_index = led->rgb_cfg->pwm_cfg->duty_cycles->start_idx,
.lut_pause_hi = led->rgb_cfg->pwm_cfg->lut_params.lut_pause_hi,
.lut_pause_lo = led->rgb_cfg->pwm_cfg->lut_params.lut_pause_lo,
.ramp_step_ms = led->rgb_cfg->pwm_cfg->lut_params.ramp_step_ms,
.flags = PM_PWM_LUT_LOOP | PM_PWM_LUT_RAMP_UP
| PM_PWM_LUT_PAUSE_HI_EN
| PM_PWM_LUT_PAUSE_LO_EN | PM_PWM_LUT_USE_RAW_VALUE,
};
int *lut = led->rgb_cfg->pwm_cfg->duty_cycles->duty_pcts;
/* set LPG offset 0x41,0x42 */
rc = pwm_config_period(led->rgb_cfg->pwm_cfg->pwm_dev, &pwm_config);
for (i = 0; i < INDEX_MAX_EACH_LED; ++i)
lut_conf.lut[i] = lut[i];
/* set LPG offset 0x40,0x46,0x50-0x57 and LUT */
rc = pwm_config_lut(led->rgb_cfg->pwm_cfg->pwm_dev, &lut_conf);
return rc;
}
static ssize_t rgbled_lut_pwm_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct qpnp_led_data *led;
struct led_classdev *led_cdev = dev_get_drvdata(dev);
ssize_t ret = -EINVAL;
int *lut;
led = container_of(led_cdev, struct qpnp_led_data, cdev);
lut = led->rgb_cfg->pwm_cfg->duty_cycles->duty_pcts;
ret = sscanf(buf, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
lut, lut+1, lut+2, lut+3, lut+4, lut+5, lut+6, lut+7,
lut+8, lut+9, lut+10, lut+11, lut+12, lut+13, lut+14, lut+15);
if (ret == 0) {
lut[0] = 0;
ret = 1;
}
led->rgb_cfg->pwm_cfg->duty_cycles->start_idx
= led->rgb_cfg->pwm_cfg->lut_params.start_idx
= (led->rgb_cfg->pwm_cfg->pwm_channel * INDEX_MAX_EACH_LED)
+ RGB_LUT_INDEX_OFFSET;
led->rgb_cfg->pwm_cfg->lut_params.idx_len = ret;
return count;
}
static ssize_t rgbled_lut_pwm_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct qpnp_led_data *led;
int *lut;
led = container_of(led_cdev, struct qpnp_led_data, cdev);
lut = led->rgb_cfg->pwm_cfg->duty_cycles->duty_pcts;
return scnprintf(buf, PAGE_SIZE, "len:%u,[0]:%d,[1]:%d\n",
led->rgb_cfg->pwm_cfg->lut_params.idx_len, lut[0], lut[1]);
}
static ssize_t rgbled_step_duration_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct qpnp_led_data *led;
struct led_classdev *led_cdev = dev_get_drvdata(dev);
ssize_t ret = -EINVAL;
unsigned long duration;
const unsigned ramp_step_duration_max = 511;
led = container_of(led_cdev, struct qpnp_led_data, cdev);
ret = kstrtoul(buf, 10, &duration);
if (ret)
return ret;
if (duration > ramp_step_duration_max)
duration = ramp_step_duration_max;
led->rgb_cfg->pwm_cfg->lut_params.ramp_step_ms = duration;
return count;
}
static ssize_t rgbled_step_duration_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct qpnp_led_data *led;
led = container_of(led_cdev, struct qpnp_led_data, cdev);
return scnprintf(buf, PAGE_SIZE, "%d\n",
led->rgb_cfg->pwm_cfg->lut_params.ramp_step_ms);
}
static ssize_t rgbled_pause_hi_multi_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct qpnp_led_data *led;
struct led_classdev *led_cdev = dev_get_drvdata(dev);
ssize_t ret = -EINVAL;
unsigned long lut_pause;
const unsigned lut_pause_max = RGB_LED_LUT_MAX_PAUSE;
led = container_of(led_cdev, struct qpnp_led_data, cdev);
ret = kstrtoul(buf, 10, &lut_pause);
if (ret)
return ret;
if (lut_pause > lut_pause_max)
led->rgb_cfg->pwm_cfg->lut_params.use_duration = true;
led->rgb_cfg->pwm_cfg->lut_params.lut_pause_hi = lut_pause;
return count;
}
static ssize_t rgbled_pause_hi_multi_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct qpnp_led_data *led;
led = container_of(led_cdev, struct qpnp_led_data, cdev);
return scnprintf(buf, PAGE_SIZE, "%d\n",
led->rgb_cfg->pwm_cfg->lut_params.lut_pause_hi);
}
static ssize_t rgbled_pause_lo_multi_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct qpnp_led_data *led;
struct led_classdev *led_cdev = dev_get_drvdata(dev);
ssize_t ret = -EINVAL;
unsigned long lut_pause;
const unsigned lut_pause_max = RGB_LED_LUT_MAX_PAUSE;
led = container_of(led_cdev, struct qpnp_led_data, cdev);
ret = kstrtoul(buf, 10, &lut_pause);
if (ret)
return ret;
if (lut_pause > lut_pause_max)
led->rgb_cfg->pwm_cfg->lut_params.use_duration = true;
led->rgb_cfg->pwm_cfg->lut_params.lut_pause_lo = lut_pause;
return count;
}
static ssize_t rgbled_pause_lo_multi_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct qpnp_led_data *led;
led = container_of(led_cdev, struct qpnp_led_data, cdev);
return scnprintf(buf, PAGE_SIZE, "%d\n",
led->rgb_cfg->pwm_cfg->lut_params.lut_pause_lo);
}
static ssize_t rgbled_single_value_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct qpnp_led_data *led;
led = container_of(led_cdev, struct qpnp_led_data, cdev);
return scnprintf(buf, PAGE_SIZE, "%d\n",
led->rgb_cfg->single_pwm_value);
}
static ssize_t rgbled_mix_value_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct qpnp_led_data *led;
led = container_of(led_cdev, struct qpnp_led_data, cdev);
return scnprintf(buf, PAGE_SIZE, "%d\n",
led->rgb_cfg->mix_pwm_value);
}
static struct device_attribute rgbled_attrs[] = {
__ATTR(lut_pwm, S_IRUGO | S_IWUSR | S_IWGRP,
rgbled_lut_pwm_show, rgbled_lut_pwm_store),
__ATTR(step_duration, S_IRUGO | S_IWUSR | S_IWGRP,
rgbled_step_duration_show, rgbled_step_duration_store),
__ATTR(pause_hi_multi, S_IRUGO | S_IWUSR | S_IWGRP,
rgbled_pause_hi_multi_show, rgbled_pause_hi_multi_store),
__ATTR(pause_lo_multi, S_IRUGO | S_IWUSR | S_IWGRP,
rgbled_pause_lo_multi_show, rgbled_pause_lo_multi_store),
__ATTR(max_single_brightness, S_IRUGO,
rgbled_single_value_show, NULL),
__ATTR(max_mix_brightness, S_IRUGO,
rgbled_mix_value_show, NULL),
__ATTR_NULL,
};
static int rgbcommon_turn_off_force(struct rgb_sync *rgb_sync)
{
int i;
int rc = 0;
for (i = 0; i < LPG_CHANNEL_MAX; ++i)
if (rgb_sync->led_data[i]) {
struct qpnp_led_data *led = rgb_sync->led_data[i];
led->cdev.brightness = 0;
led->rgb_cfg->pwm_cfg->lut_params.idx_len = 0;
rc = qpnp_led_masked_write(led,
RGB_LED_EN_CTL(led->base),
led->rgb_cfg->enable, RGB_LED_DISABLE);
if (rc)
break;
}
return rc;
}
static ssize_t rgbcommon_sync_state_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct rgb_sync *rgb_sync;
unsigned long state;
struct led_classdev *led_cdev = dev_get_drvdata(dev);
ssize_t ret = -EINVAL;
int i;
int rc;
enum led_mode led_mode;
enum pm_pwm_mode pwm_mode;
ret = kstrtoul(buf, 10, &state);
if (ret)
return ret;
if (state > RGBSYNC_STATE_BLINKING) {
dev_err(dev, "%s out of range\n", __func__);
return -EFAULT;
}
rgb_sync = container_of(led_cdev, struct rgb_sync, cdev);
if (!rgb_sync) {
dev_err(dev, "%s rgb_sync NULL\n", __func__);
return -EFAULT;
}
if (rgb_sync->sync_state == state) {
dev_err(dev, "%s do nothing. same state", __func__);
return count;
} else if ((rgb_sync->sync_state == RGBSYNC_STATE_BLINKING)
|| ((rgb_sync->sync_state == RGBSYNC_STATE_NOT_BLINK)
&& (state == RGBSYNC_STATE_CONFIGURE_TO_BLINK))) {
dev_err(dev, "%s turn off force. state changed\n", __func__);
rc = rgbcommon_turn_off_force(rgb_sync);
if (rc)
dev_err(dev, "Failed to turn off force\n");
}
switch (rgb_sync->sync_state = state) {
case RGBSYNC_STATE_NOT_BLINK:
led_mode = PWM_DIRECT_MODE;
pwm_mode = PM_PWM_MODE_PWM;
break;
case RGBSYNC_STATE_CONFIGURE_TO_BLINK:
led_mode = LPG_SYNC_MODE;
pwm_mode = PM_PWM_MODE_LPG;
break;
default:
return count;
break;
}
for (i = 0; i < LPG_CHANNEL_MAX; ++i)
if (rgb_sync->led_data[i]) {
struct qpnp_led_data *led = rgb_sync->led_data[i];
led->rgb_cfg->pwm_cfg->mode = led_mode;
rc = pwm_change_mode(led->rgb_cfg->pwm_cfg->pwm_dev,
pwm_mode);
if (rc)
break;
}
return count;
}
static ssize_t rgbcommon_sync_state_show(struct device *ldev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(ldev);
struct device *dev = led_cdev->dev->parent;
struct rgb_sync *rgb_sync;
rgb_sync = container_of(led_cdev, struct rgb_sync, cdev);
if (!rgb_sync) {
dev_err(dev, "%s rgb_sync NULL\n", __func__);
return -EFAULT;
}
return scnprintf(buf, PAGE_SIZE, "%u\n", rgb_sync->sync_state);
}
static ssize_t rgbcommon_start_blink_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct rgb_sync *rgb_sync;
struct led_classdev *led_cdev = dev_get_drvdata(dev);
int i;
struct qpnp_led_data *led = NULL;
int rc;
int ramp_control = 0;
rgb_sync = container_of(led_cdev, struct rgb_sync, cdev);
if (!rgb_sync) {
dev_err(dev, "%s rgb_sync NULL\n", __func__);
return -EFAULT;
}
if (rgb_sync->sync_state != RGBSYNC_STATE_CONFIGURE_TO_BLINK) {
dev_err(dev, "%s ignore. not configure to blink state\n",
__func__);
return count;
}
rgb_sync->sync_state = RGBSYNC_STATE_BLINKING;
/* must set only led which is blinked */
for (i = 0; i < LPG_CHANNEL_MAX; ++i) {
if (!rgb_sync->led_data[i])
continue;
led = rgb_sync->led_data[i];
if (led->rgb_cfg->pwm_cfg->lut_params.use_duration)
qpnp_rgb_set_duration(led);
if (led->rgb_cfg->pwm_cfg->lut_params.idx_len) {
rgbled_set_lut_to_register(led);
ramp_control |= (1 << i);
rc = qpnp_led_masked_write(led,
RGB_LED_EN_CTL(led->base),
led->rgb_cfg->enable, led->rgb_cfg->enable);
}
}
if (led) {
rc = pwm_start_lut_ramp(led->rgb_cfg->pwm_cfg->pwm_dev,
ramp_control);
if (rc)
dev_err(dev, "Failed to start blink\n");
} else {
dev_err(dev, "try to start blink. however not configured.\n");
}
return count;
}
static struct device_attribute rgbcommon_attr[] = {
__ATTR(sync_state, S_IRUGO | S_IWUSR | S_IWGRP,
rgbcommon_sync_state_show, rgbcommon_sync_state_store),
__ATTR(start_blink, S_IWUSR | S_IWGRP,
NULL, rgbcommon_start_blink_store),
__ATTR_NULL,
};
static void qpnp_remove_attributes(struct device *dev,
struct device_attribute *attrs)
{
int i;
for (i = 0; attrs[i].attr.name; i++)
device_remove_file(dev, &attrs[i]);
}
static int qpnp_add_attributes(struct device *dev,
struct device_attribute *attrs)
{
int error = 0;
int i;
for (i = 0; attrs[i].attr.name; i++) {
error = device_create_file(dev, &attrs[i]);
if (error) {
dev_err(dev, "%s: failed.\n", __func__);
goto err;
}
}
return 0;
err:
while (--i >= 0)
device_remove_file(dev, &attrs[i]);
return error;
}
static int qpnp_rgb_init(struct qpnp_led_data *led)
{
int rc;
int max_pwm_value;
rc = qpnp_led_masked_write(led, RGB_LED_SRC_SEL(led->base),
RGB_LED_SRC_MASK, RGB_LED_SOURCE_VPH_PWR);
@ -2975,6 +3535,24 @@ static int qpnp_rgb_init(struct qpnp_led_data *led)
dev_err(&led->pdev->dev, "Failed to initialize pwm\n");
return rc;
}
if (led->rgb_sync) {
led->rgb_sync->led_data[led->rgb_cfg->pwm_cfg->pwm_channel]
= led;
led->rgb_cfg->pwm_cfg->mode = PWM_DIRECT_MODE;
rc = pwm_change_mode(led->rgb_cfg->pwm_cfg->pwm_dev,
PM_PWM_MODE_PWM);
if (rc < 0) {
dev_err(&led->pdev->dev, "Failed to " \
"configure pwm mode\n");
return rc;
}
}
pwm_set_max_pwm_value(led->rgb_cfg->pwm_cfg->pwm_dev, led->max_current);
max_pwm_value = pwm_get_max_pwm_value(led->rgb_cfg->pwm_cfg->pwm_dev);
led->max_current
= max_pwm_value ? max_pwm_value : RGB_MAX_LEVEL;
/* Initialize led for use in auto trickle charging mode */
rc = qpnp_led_masked_write(led, RGB_LED_ATC_CTL(led->base),
led->rgb_cfg->enable, led->rgb_cfg->enable);
@ -3631,13 +4209,13 @@ static int qpnp_get_config_kpdbl(struct qpnp_led_data *led,
return rc;
led->kpdbl_cfg->row_src_vbst =
of_property_read_bool(node, "qcom,row-src-vbst");
of_property_read_bool(node, "qcom,row-src-vbst");
led->kpdbl_cfg->row_src_en =
of_property_read_bool(node, "qcom,row-src-en");
of_property_read_bool(node, "qcom,row-src-en");
led->kpdbl_cfg->always_on =
of_property_read_bool(node, "qcom,always-on");
of_property_read_bool(node, "qcom,always-on");
return 0;
}
@ -3645,7 +4223,9 @@ static int qpnp_get_config_kpdbl(struct qpnp_led_data *led,
static int qpnp_get_config_rgb(struct qpnp_led_data *led,
struct device_node *node)
{
int rc;
int rc, current_index = 0, i;
u32 val, color_variation_max_num;
u32 *rgb_current_table;
u8 led_mode;
const char *mode;
@ -3684,6 +4264,82 @@ static int qpnp_get_config_rgb(struct qpnp_led_data *led,
return rc;
}
rc = of_property_read_u32(node,
"somc,color_variation_max_num",
&color_variation_max_num);
if (rc < 0) {
dev_err(&led->pdev->dev, "Unable to read color_variation_max_num\n");
color_variation_max_num = RGB_CURR_DEFAULT_PATTERN;
}
dev_info(&led->pdev->dev, "color_variation_max_num[%d] rgb_current_index[%d]\n",
color_variation_max_num, rgb_current_index);
rgb_current_table = devm_kzalloc(&led->pdev->dev,
sizeof(u32) * color_variation_max_num * RGB_CURR_UNIT_NUM,
GFP_KERNEL);
if (rgb_current_table) {
rc = of_property_read_u32_array(node, "somc,max_current",
rgb_current_table,
color_variation_max_num * RGB_CURR_UNIT_NUM);
if (rc < 0) {
dev_err(&led->pdev->dev, "Unable to read max_mix_current\n");
led->rgb_cfg->single_pwm_value = RGB_MAX_LEVEL - 1;
led->rgb_cfg->mix_pwm_value = RGB_MAX_LEVEL - 1;
} else {
for (i = 0; i < color_variation_max_num; i++) {
current_index = i * RGB_CURR_UNIT_NUM;
if (rgb_current_index == rgb_current_table[current_index]) {
break;
}
}
led->rgb_cfg->single_pwm_value =
rgb_current_table[current_index + 1];
led->rgb_cfg->mix_pwm_value =
rgb_current_table[current_index + 2];
}
} else {
dev_err(&led->pdev->dev, "Unable to allocate memory\n");
led->rgb_cfg->single_pwm_value = RGB_MAX_LEVEL - 1;
led->rgb_cfg->mix_pwm_value = RGB_MAX_LEVEL - 1;
}
devm_kfree(&led->pdev->dev, rgb_current_table);
led->max_current = (led->rgb_cfg->single_pwm_value >
led->rgb_cfg->mix_pwm_value ?
led->rgb_cfg->single_pwm_value :
led->rgb_cfg->mix_pwm_value);
dev_info(&led->pdev->dev, "single_pwm_value[%d] mix_pwm_value[%d]\n",
led->rgb_cfg->single_pwm_value, led->rgb_cfg->mix_pwm_value);
dev_info(&led->pdev->dev, "max_current[%d]\n", led->max_current);
if (led->rgb_sync) {
rc = of_property_read_u32(node, "somc,pwm-channel", &val);
if (!rc)
led->rgb_cfg->pwm_cfg->pwm_channel = val;
else
return rc;
/* allocate memory for simple use case */
led->rgb_cfg->pwm_cfg->duty_cycles =
devm_kzalloc(&led->pdev->dev,
sizeof(struct pwm_duty_cycles), GFP_KERNEL);
if (!led->rgb_cfg->pwm_cfg->duty_cycles) {
dev_err(&led->pdev->dev,
"Unable to allocate memory\n");
return -ENOMEM;
}
led->rgb_cfg->pwm_cfg->duty_cycles->duty_pcts =
devm_kzalloc(&led->pdev->dev,
sizeof(int) * INDEX_MAX_EACH_LED, GFP_KERNEL);
if (!led->rgb_cfg->pwm_cfg->duty_cycles->duty_pcts) {
dev_err(&led->pdev->dev,
"Unable to allocate memory\n");
return -ENOMEM;
}
led->rgb_cfg->pwm_cfg->pwm_dev = of_pwm_get(node, NULL);
return 0;
}
rc = qpnp_get_config_pwm(led->rgb_cfg->pwm_cfg, led->pdev, node);
if (rc < 0) {
if (led->rgb_cfg->pwm_cfg->pwm_dev)
@ -3872,6 +4528,8 @@ static int qpnp_leds_probe(struct platform_device *pdev)
int rc, i, num_leds = 0, parsed_leds = 0;
const char *led_label;
bool regulator_probe = false;
int prepare_rgb_sync;
struct rgb_sync *rgb_sync = NULL;
node = pdev->dev.of_node;
if (node == NULL)
@ -3889,10 +4547,34 @@ static int qpnp_leds_probe(struct platform_device *pdev)
if (!led_array)
return -ENOMEM;
rc = of_property_read_u32(node, "qcom,rgb_sync", &prepare_rgb_sync);
if (rc < 0)
prepare_rgb_sync = 0;
if (prepare_rgb_sync)
rgb_sync = devm_kzalloc(&pdev->dev,
sizeof(struct rgb_sync), GFP_KERNEL);
if (rgb_sync) {
rgb_sync->cdev.name = "rgb";
rgb_sync->pdev = pdev;
rc = led_classdev_register(&pdev->dev, &rgb_sync->cdev);
if (rc) {
dev_err(&pdev->dev, "unable to register rgb %d\n", rc);
rgb_sync = 0;
}
rc = rc ? rc :
qpnp_add_attributes(rgb_sync->cdev.dev, rgbcommon_attr);
if (rc) {
dev_err(&pdev->dev, "unable to create sysfs %d\n", rc);
rgb_sync = 0;
}
}
dev_info(&pdev->dev, "rgb_sync prepare %d\n", prepare_rgb_sync);
for_each_child_of_node(node, temp) {
led = &led_array[parsed_leds];
led->num_leds = num_leds;
led->regmap = dev_get_regmap(pdev->dev.parent, NULL);
led->rgb_sync = rgb_sync;
if (!led->regmap) {
dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
return -EINVAL;
@ -4048,6 +4730,14 @@ static int qpnp_leds_probe(struct platform_device *pdev)
}
if (led->id == QPNP_ID_RGB_RED ||
led->id == QPNP_ID_RGB_GREEN ||
led->id == QPNP_ID_RGB_BLUE) {
rc = qpnp_add_attributes(led->cdev.dev, rgbled_attrs);
if (rc)
goto fail_id_check;
}
if (led->id == QPNP_ID_LED_MPP) {
if (!led->mpp_cfg->pwm_cfg)
break;
@ -4182,6 +4872,8 @@ static int qpnp_leds_remove(struct platform_device *pdev)
case QPNP_ID_RGB_RED:
case QPNP_ID_RGB_GREEN:
case QPNP_ID_RGB_BLUE:
qpnp_remove_attributes(led_array[i].cdev.dev,
rgbled_attrs);
if (led_array[i].rgb_cfg->pwm_cfg->mode == PWM_MODE)
sysfs_remove_group(&led_array[i].cdev.dev->kobj,
&pwm_attr_group);