blob: b41ea776919662a94583d4cb11d148ea51d4df48 [file] [log] [blame]
/*
* leds-aw210xx.c
*
* Copyright (c) 2021 AWINIC Technology CO., LTD
*
* Author: hushanping <hushanping@awinic.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/i2c.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/firmware.h>
#include <linux/slab.h>
#include <linux/version.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/debugfs.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/leds.h>
#include "leds_aw210xx.h"
#include "leds_aw210xx_reg.h"
/******************************************************
*
* Macro
*
******************************************************/
#define AW210XX_DRIVER_VERSION "V0.2.0"
#define AW_I2C_RETRIES 5
#define AW_I2C_RETRY_DELAY 1
#define AW_READ_CHIPID_RETRIES 2
#define AW_READ_CHIPID_RETRY_DELAY 1
#define AW210XX_CFG_NAME_MAX 64
#define MAX_CURRENT_LIMIT_PERCENT (100)
#define MAX_PWM_SCALE (255)
#define MAX_GLOBAL_DIMMING (255)
#define AW210XX_ATTR_NAME_LEN_MAX (8)
/******************************************************
*
* aw210xx led parameter
*
******************************************************/
aw210xx_cfg_t aw210xx_cfg_array[] = {
{aw210xx_group_cfg_led_off, sizeof(aw210xx_group_cfg_led_off)},
{aw21018_group_all_leds_on, sizeof(aw21018_group_all_leds_on)},
{aw21018_group_red_leds_on, sizeof(aw21018_group_red_leds_on)},
{aw21018_group_green_leds_on, sizeof(aw21018_group_green_leds_on)},
{aw21018_group_blue_leds_on, sizeof(aw21018_group_blue_leds_on)},
{aw21018_group_breath_leds_on, sizeof(aw21018_group_breath_leds_on)},
{aw21012_group_all_leds_on, sizeof(aw21012_group_all_leds_on)},
{aw21012_group_red_leds_on, sizeof(aw21012_group_red_leds_on)},
{aw21012_group_green_leds_on, sizeof(aw21012_group_green_leds_on)},
{aw21012_group_blue_leds_on, sizeof(aw21012_group_blue_leds_on)},
{aw21012_group_breath_leds_on, sizeof(aw21012_group_breath_leds_on)},
{aw21009_group_all_leds_on, sizeof(aw21009_group_all_leds_on)},
{aw21009_group_red_leds_on, sizeof(aw21009_group_red_leds_on)},
{aw21009_group_green_leds_on, sizeof(aw21009_group_green_leds_on)},
{aw21009_group_blue_leds_on, sizeof(aw21009_group_blue_leds_on)},
{aw21009_group_breath_leds_on, sizeof(aw21009_group_breath_leds_on)}};
static char aw210xx_cfg_name[][AW210XX_CFG_NAME_MAX] = {
{"aw210xx_group_cfg_led_off"},
{"aw21018_group_all_leds_on"},
{"aw21018_group_red_leds_on"},
{"aw21018_group_green_leds_on"},
{"aw21018_group_blue_leds_on"},
{"aw21018_group_breath_leds_on"},
{"aw21012_group_all_leds_on"},
{"aw21012_group_red_leds_on"},
{"aw21012_group_green_leds_on"},
{"aw21012_group_blue_leds_on"},
{"aw21012_group_breath_leds_on"},
{"aw21009_group_all_leds_on"},
{"aw21009_group_red_leds_on"},
{"aw21009_group_green_leds_on"},
{"aw21009_group_blue_leds_on"},
{"aw21009_group_breath_leds_on"},
};
/*Attributes for the individual LED PWM and IREF sysfs nodes*/
static struct device_attribute aw210xx_iref_pwm_attrs[2 * AW21018_MAX_RGB_LED];
static struct attribute *all_generated_attrs[2 * AW21018_MAX_RGB_LED + 1];
static struct attribute_group iref_pwm_attr_group;
static char aw210xx_attr_names[2 * AW21018_MAX_RGB_LED]
[AW210XX_ATTR_NAME_LEN_MAX + 1];
static int skip = 0;
module_param(skip, int, 0444);
/******************************************************
*
* aw210xx i2c write/read
*
******************************************************/
static int aw210xx_i2c_write(struct aw210xx *aw210xx,
unsigned char reg_addr,
unsigned char reg_data)
{
int ret = -1;
unsigned char cnt = 0;
while (cnt < AW_I2C_RETRIES) {
ret = i2c_smbus_write_byte_data(aw210xx->i2c,
reg_addr,
reg_data);
if (ret < 0)
AW_ERR("i2c_write cnt=%d ret=%d\n", cnt, ret);
else
break;
cnt++;
usleep_range(AW_I2C_RETRY_DELAY * 1000,
AW_I2C_RETRY_DELAY * 1000 + 500);
}
return ret;
}
static int aw210xx_i2c_write_block(struct aw210xx *aw210xx,
unsigned char reg_addr,
unsigned char len,
unsigned char *reg_data)
{
int ret = -1;
unsigned char cnt = 0;
while (cnt < AW_I2C_RETRIES) {
ret = i2c_smbus_write_i2c_block_data(aw210xx->i2c,
reg_addr,
len,
reg_data);
if (ret < 0)
AW_ERR("i2c_write_block cnt=%d ret=%d\n", cnt, ret);
else
break;
cnt++;
usleep_range(AW_I2C_RETRY_DELAY * 1000,
AW_I2C_RETRY_DELAY * 1000 + 500);
}
return ret;
}
static int aw210xx_i2c_read(struct aw210xx *aw210xx,
unsigned char reg_addr,
unsigned char *reg_data)
{
int ret = -1;
unsigned char cnt = 0;
while (cnt < AW_I2C_RETRIES) {
ret = i2c_smbus_read_byte_data(aw210xx->i2c, reg_addr);
if (ret < 0) {
AW_ERR("i2c_read cnt=%d ret=%d\n", cnt, ret);
} else {
*reg_data = ret;
break;
}
cnt++;
usleep_range(AW_I2C_RETRY_DELAY * 1000,
AW_I2C_RETRY_DELAY * 1000 + 500);
}
return ret;
}
static int aw210xx_i2c_read_block(struct aw210xx *aw210xx,
unsigned char reg_addr,
unsigned char len,
unsigned char *reg_data)
{
int ret = -1;
unsigned char cnt = 0;
while (cnt < AW_I2C_RETRIES) {
ret = i2c_smbus_read_i2c_block_data(aw210xx->i2c,
reg_addr,
len,
reg_data);
if (ret < 0) {
AW_ERR("i2c_read cnt=%d ret=%d\n", cnt, ret);
} else {
*reg_data = ret;
break;
}
cnt++;
usleep_range(AW_I2C_RETRY_DELAY * 1000,
AW_I2C_RETRY_DELAY * 1000 + 500);
}
return ret;
}
static int aw210xx_i2c_write_bits(struct aw210xx *aw210xx,
unsigned char reg_addr,
unsigned int mask,
unsigned char reg_data)
{
unsigned char reg_val;
aw210xx_i2c_read(aw210xx, reg_addr, &reg_val);
reg_val &= mask;
reg_val |= (reg_data & (~mask));
aw210xx_i2c_write(aw210xx, reg_addr, reg_val);
return 0;
}
/*****************************************************
* led Interface: set effect
*****************************************************/
static void aw210xx_update_cfg_array(struct aw210xx *aw210xx,
uint8_t *p_cfg_data,
uint32_t cfg_size)
{
unsigned int i = 0;
for (i = 0; i < cfg_size; i += 2)
aw210xx_i2c_write(aw210xx, p_cfg_data[i], p_cfg_data[i + 1]);
}
void aw210xx_cfg_update(struct aw210xx *aw210xx)
{
AW_LOG("aw210xx->effect = %d", aw210xx->effect);
aw210xx_update_cfg_array(aw210xx,
aw210xx_cfg_array[aw210xx->effect].p,
aw210xx_cfg_array[aw210xx->effect].count);
}
void aw210xx_uvlo_set(struct aw210xx *aw210xx, bool flag)
{
if (flag) {
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_UVCR,
AW210XX_BIT_UVPD_MASK,
AW210XX_BIT_UVPD_DISENA);
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_UVCR,
AW210XX_BIT_UVDIS_MASK,
AW210XX_BIT_UVDIS_DISENA);
} else {
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_UVCR,
AW210XX_BIT_UVPD_MASK,
AW210XX_BIT_UVPD_ENABLE);
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_UVCR,
AW210XX_BIT_UVDIS_MASK,
AW210XX_BIT_UVDIS_ENABLE);
}
}
void aw210xx_sbmd_set(struct aw210xx *aw210xx, bool flag)
{
if (flag) {
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR2,
AW210XX_BIT_SBMD_MASK,
AW210XX_BIT_SBMD_ENABLE);
aw210xx->sdmd_flag = 1;
} else {
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR2,
AW210XX_BIT_SBMD_MASK,
AW210XX_BIT_SBMD_DISENA);
aw210xx->sdmd_flag = 0;
}
}
void aw210xx_rgbmd_set(struct aw210xx *aw210xx, bool flag)
{
if (flag) {
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR2,
AW210XX_BIT_RGBMD_MASK,
AW210XX_BIT_RGBMD_ENABLE);
aw210xx->rgbmd_flag = 1;
} else {
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR2,
AW210XX_BIT_RGBMD_MASK,
AW210XX_BIT_RGBMD_DISENA);
aw210xx->rgbmd_flag = 0;
}
}
void aw210xx_apse_set(struct aw210xx *aw210xx, bool flag)
{
if (flag) {
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR,
AW210XX_BIT_APSE_MASK,
AW210XX_BIT_APSE_ENABLE);
} else {
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR,
AW210XX_BIT_APSE_MASK,
AW210XX_BIT_APSE_DISENA);
}
}
/*****************************************************
* aw210xx led function set
*****************************************************/
int32_t aw210xx_osc_pwm_set(struct aw210xx *aw210xx)
{
switch (aw210xx->osc_clk) {
case CLK_FRQ_16M:
AW_LOG("osc is 16MHz!\n");
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR,
AW210XX_BIT_CLKFRQ_MASK,
AW210XX_BIT_CLKFRQ_16MHz);
break;
case CLK_FRQ_8M:
AW_LOG("osc is 8MHz!\n");
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR,
AW210XX_BIT_CLKFRQ_MASK,
AW210XX_BIT_CLKFRQ_8MHz);
break;
case CLK_FRQ_1M:
AW_LOG("osc is 1MHz!\n");
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR,
AW210XX_BIT_CLKFRQ_MASK,
AW210XX_BIT_CLKFRQ_1MHz);
break;
case CLK_FRQ_512k:
AW_LOG("osc is 512KHz!\n");
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR,
AW210XX_BIT_CLKFRQ_MASK,
AW210XX_BIT_CLKFRQ_512kHz);
break;
case CLK_FRQ_256k:
AW_LOG("osc is 256KHz!\n");
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR,
AW210XX_BIT_CLKFRQ_MASK,
AW210XX_BIT_CLKFRQ_256kHz);
break;
case CLK_FRQ_125K:
AW_LOG("osc is 125KHz!\n");
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR,
AW210XX_BIT_CLKFRQ_MASK,
AW210XX_BIT_CLKFRQ_125kHz);
break;
case CLK_FRQ_62_5K:
AW_LOG("osc is 62.5KHz!\n");
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR,
AW210XX_BIT_CLKFRQ_MASK,
AW210XX_BIT_CLKFRQ_62_5kHz);
break;
case CLK_FRQ_31_25K:
AW_LOG("osc is 31.25KHz!\n");
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR,
AW210XX_BIT_CLKFRQ_MASK,
AW210XX_BIT_CLKFRQ_31_25kHz);
break;
default:
AW_LOG("this clk_pwm is unsupported!\n");
return -AW210XX_CLK_MODE_UNSUPPORT;
}
return 0;
}
int32_t aw210xx_br_res_set(struct aw210xx *aw210xx)
{
switch (aw210xx->br_res) {
case BR_RESOLUTION_8BIT:
AW_LOG("br resolution select 8bit!\n");
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR,
AW210XX_BIT_PWMRES_MASK,
AW210XX_BIT_PWMRES_8BIT);
break;
case BR_RESOLUTION_9BIT:
AW_LOG("br resolution select 9bit!\n");
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR,
AW210XX_BIT_PWMRES_MASK,
AW210XX_BIT_PWMRES_9BIT);
break;
case BR_RESOLUTION_12BIT:
AW_LOG("br resolution select 12bit!\n");
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR,
AW210XX_BIT_PWMRES_MASK,
AW210XX_BIT_PWMRES_12BIT);
break;
case BR_RESOLUTION_9_AND_3_BIT:
AW_LOG("br resolution select 9+3bit!\n");
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR,
AW210XX_BIT_PWMRES_MASK,
AW210XX_BIT_PWMRES_9_AND_3_BIT);
break;
default:
AW_LOG("this br_res is unsupported!\n");
return -AW210XX_CLK_MODE_UNSUPPORT;
}
return 0;
}
int32_t aw210xx_pde_set(struct aw210xx *aw210xx)
{
switch (aw210xx->pde) {
case AW21018_GENERIC_DISABLE:
AW_LOG("phase delay disabled!\n");
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_PHCR,
AW210XX_BIT_PDE_MASK,
AW210XX_BIT_PDE_DISABLE);
break;
case AW21018_GENERIC_ENABLE:
AW_LOG("phase delay enabled!\n");
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_PHCR,
AW210XX_BIT_PDE_MASK,
AW210XX_BIT_PDE_ENABLE);
break;
default:
AW_LOG("this pde (phase delay enable) is unsupported!\n");
return -AW210XX_INPUT_ERROR;
}
return 0;
}
/*****************************************************
* aw210xx debug interface set
*****************************************************/
static void aw210xx_update(struct aw210xx *aw210xx)
{
aw210xx_i2c_write(aw210xx, AW210XX_REG_UPDATE, AW210XX_UPDATE_BR_SL);
}
void aw210xx_global_set(struct aw210xx *aw210xx)
{
aw210xx_i2c_write(aw210xx, AW210XX_REG_GCCR, aw210xx->glo_current);
}
void aw210xx_current_set(struct aw210xx *aw210xx)
{
aw210xx_i2c_write(aw210xx, AW210XX_REG_GCCR, aw210xx->set_current);
}
/*****************************************************
*
* aw210xx led cfg
*
*****************************************************/
static void aw210xx_brightness_work(struct work_struct *work)
{
struct aw210xx *aw210xx =
container_of(work, struct aw210xx, brightness_work);
AW_LOG("enter\n");
if (aw210xx->cdev.brightness > aw210xx->cdev.max_brightness)
aw210xx->cdev.brightness = aw210xx->cdev.max_brightness;
aw210xx_i2c_write(aw210xx, AW210XX_REG_GCCR, aw210xx->cdev.brightness);
}
static void aw210xx_set_brightness(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct aw210xx *aw210xx = container_of(cdev, struct aw210xx, cdev);
aw210xx->cdev.brightness = brightness;
schedule_work(&aw210xx->brightness_work);
}
/*****************************************************
* aw210xx basic function set
*****************************************************/
void aw210xx_chipen_set(struct aw210xx *aw210xx, bool flag)
{
if (flag) {
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR,
AW210XX_BIT_CHIPEN_MASK,
AW210XX_BIT_CHIPEN_ENABLE);
} else {
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_GCR,
AW210XX_BIT_CHIPEN_MASK,
AW210XX_BIT_CHIPEN_DISENA);
}
}
static int aw210xx_hw_enable(struct aw210xx *aw210xx, bool flag)
{
int ret;
AW_LOG("enter\n");
if (!aw210xx || !gpio_is_valid(aw210xx->enable_gpio)) {
AW_ERR("Write enable gpio failed\n");
return -EINVAL;
}
ret = devm_gpio_request_one(aw210xx->dev,
aw210xx->enable_gpio,
GPIOF_OUT_INIT_LOW,
"aw210xx_en");
if (ret) {
AW_ERR("Request enable gpio failed\n");
return ret;
}
if (flag) {
gpio_set_value_cansleep(aw210xx->enable_gpio, 1);
usleep_range(2000, 2500);
} else {
gpio_set_value_cansleep(aw210xx->enable_gpio, 0);
}
devm_gpio_free(aw210xx->dev, aw210xx->enable_gpio);
return 0;
}
static int aw210xx_led_init(struct aw210xx *aw210xx)
{
AW_LOG("enter\n");
aw210xx->sdmd_flag = 0;
aw210xx->rgbmd_flag = 0;
/* chip enable */
aw210xx_chipen_set(aw210xx, true);
/* sbmd enable */
aw210xx_sbmd_set(aw210xx, true);
/* rgbmd enable */
aw210xx_rgbmd_set(aw210xx, true);
/* clk_pwm selsect */
aw210xx_osc_pwm_set(aw210xx);
/* br_res select */
aw210xx_br_res_set(aw210xx);
/* pde select */
aw210xx_pde_set(aw210xx);
/* global set */
aw210xx_global_set(aw210xx);
/* under voltage lock out */
aw210xx_uvlo_set(aw210xx, true);
/* apse enable */
aw210xx_apse_set(aw210xx, true);
return 0;
}
static int32_t aw210xx_group_gcfg_set(struct aw210xx *aw210xx, bool flag)
{
if (flag) {
switch (aw210xx->chipid) {
case AW21018_CHIPID:
aw210xx_i2c_write(aw210xx,
AW210XX_REG_GCFG,
AW21018_GROUP_ENABLE);
return 0;
case AW21012_CHIPID:
aw210xx_i2c_write(aw210xx,
AW210XX_REG_GCFG,
AW21012_GROUP_ENABLE);
return 0;
case AW21009_CHIPID:
aw210xx_i2c_write(aw210xx,
AW210XX_REG_GCFG,
AW21009_GROUP_ENABLE);
return 0;
default:
AW_LOG("%s: chip is unsupported device!\n", __func__);
return -AW210XX_CHIPID_FAILD;
}
} else {
switch (aw210xx->chipid) {
case AW21018_CHIPID:
aw210xx_i2c_write(aw210xx,
AW210XX_REG_GCFG,
AW21018_GROUP_DISABLE);
return 0;
case AW21012_CHIPID:
aw210xx_i2c_write(aw210xx,
AW210XX_REG_GCFG,
AW21012_GROUP_DISABLE);
return 0;
case AW21009_CHIPID:
aw210xx_i2c_write(aw210xx,
AW210XX_REG_GCFG,
AW21009_GROUP_DISABLE);
return 0;
default:
AW_LOG("%s: chip is unsupported device!\n", __func__);
return -AW210XX_CHIPID_FAILD;
}
}
}
void aw210xx_singleled_set(struct aw210xx *aw210xx,
uint32_t rgb_reg,
uint32_t rgb_sl,
uint32_t rgb_br)
{
/* chip enable */
aw210xx_chipen_set(aw210xx, true);
/* global set */
aw210xx->set_current = rgb_br;
aw210xx_current_set(aw210xx);
/* group set disable */
aw210xx_group_gcfg_set(aw210xx, false);
aw210xx_sbmd_set(aw210xx, true);
aw210xx_rgbmd_set(aw210xx, false);
aw210xx_uvlo_set(aw210xx, true);
/* set sl */
aw210xx->rgbcolor = rgb_sl & 0xff;
aw210xx_i2c_write(aw210xx,
AW210XX_REG_SL00 + rgb_reg,
aw210xx->rgbcolor);
/* br set */
if (aw210xx->sdmd_flag == 1) {
if (aw210xx->rgbmd_flag == 1) {
aw210xx_i2c_write(aw210xx,
AW210XX_REG_BR00L + rgb_reg,
rgb_br);
} else {
aw210xx_i2c_write(aw210xx,
AW210XX_REG_BR00L + rgb_reg,
rgb_br);
}
} else {
if (aw210xx->rgbmd_flag == 1) {
aw210xx_i2c_write(aw210xx,
AW210XX_REG_BR00L + rgb_reg,
rgb_br);
aw210xx_i2c_write(aw210xx,
AW210XX_REG_BR00H + rgb_reg,
rgb_br);
} else {
aw210xx_i2c_write(aw210xx,
AW210XX_REG_BR00L + rgb_reg,
rgb_br);
aw210xx_i2c_write(aw210xx,
AW210XX_REG_BR00H + rgb_reg,
rgb_br);
}
}
/* update */
aw210xx_update(aw210xx);
}
/*****************************************************
* open short detect
*****************************************************/
void aw210xx_open_detect_cfg(struct aw210xx *aw210xx)
{
/*enable open detect*/
aw210xx_i2c_write(aw210xx, AW210XX_REG_OSDCR, AW210XX_OPEN_DETECT_EN);
/*set DCPWM = 1*/
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_SSCR,
AW210XX_DCPWM_SET_MASK,
AW210XX_DCPWM_SET);
/*set Open threshold = 0.2v*/
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_OSDCR,
AW210XX_OPEN_THRESHOLD_SET_MASK,
AW210XX_OPEN_THRESHOLD_SET);
}
void aw210xx_short_detect_cfg(struct aw210xx *aw210xx)
{
/*enable short detect*/
aw210xx_i2c_write(aw210xx, AW210XX_REG_OSDCR, AW210XX_SHORT_DETECT_EN);
/*set DCPWM = 1*/
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_SSCR,
AW210XX_DCPWM_SET_MASK,
AW210XX_DCPWM_SET);
/*set Short threshold = 1v*/
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_OSDCR,
AW210XX_SHORT_THRESHOLD_SET_MASK,
AW210XX_SHORT_THRESHOLD_SET);
}
void aw210xx_open_short_dis(struct aw210xx *aw210xx)
{
aw210xx_i2c_write(aw210xx, AW210XX_REG_OSDCR, AW210XX_OPEN_SHORT_DIS);
/*SET DCPWM = 0*/
aw210xx_i2c_write_bits(aw210xx,
AW210XX_REG_SSCR,
AW210XX_DCPWM_SET_MASK,
AW210XX_DCPWM_CLEAN);
}
void aw210xx_open_short_detect(struct aw210xx *aw210xx,
int32_t detect_flg,
u8 *reg_val)
{
/*config for open shor detect*/
if (detect_flg == AW210XX_OPEN_DETECT)
aw210xx_open_detect_cfg(aw210xx);
else if (detect_flg == AW210XX_SHORT_DETECT)
aw210xx_short_detect_cfg(aw210xx);
/*read detect result*/
aw210xx_i2c_read(aw210xx, AW210XX_REG_OSST0, &reg_val[0]);
aw210xx_i2c_read(aw210xx, AW210XX_REG_OSST1, &reg_val[1]);
aw210xx_i2c_read(aw210xx, AW210XX_REG_OSST2, &reg_val[2]);
/*close for open short detect*/
aw210xx_open_short_dis(aw210xx);
}
/******************************************************
*
* sys group attribute: reg
*
******************************************************/
static ssize_t aw210xx_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 aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
uint32_t databuf[2] = {0, 0};
if (sscanf(buf, "%x %x", &databuf[0], &databuf[1]) == 2) {
if (aw210xx_reg_access[(uint8_t)databuf[0]] & REG_WR_ACCESS)
aw210xx_i2c_write(aw210xx,
(uint8_t)databuf[0],
(uint8_t)databuf[1]);
}
return len;
}
static ssize_t
aw210xx_reg_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
ssize_t len = 0;
unsigned int i = 0;
unsigned char reg_val = 0;
uint8_t br_max = 0;
uint8_t sl_val = 0;
aw210xx_i2c_read(aw210xx, AW210XX_REG_GCR, &reg_val);
len += snprintf(buf + len,
PAGE_SIZE - len,
"reg:0x%02x=0x%02x\n",
AW210XX_REG_GCR,
reg_val);
switch (aw210xx->chipid) {
case AW21018_CHIPID:
br_max = AW210XX_REG_BR17H;
sl_val = AW210XX_REG_SL17;
break;
case AW21012_CHIPID:
br_max = AW210XX_REG_BR11H;
sl_val = AW210XX_REG_SL11;
break;
case AW21009_CHIPID:
br_max = AW210XX_REG_BR08H;
sl_val = AW210XX_REG_SL08;
break;
default:
AW_LOG("chip is unsupported device!\n");
return len;
}
for (i = AW210XX_REG_BR00L; i <= br_max; i++) {
if (!(aw210xx_reg_access[i] & REG_RD_ACCESS))
continue;
aw210xx_i2c_read(aw210xx, i, &reg_val);
len += snprintf(buf + len,
PAGE_SIZE - len,
"reg:0x%02x=0x%02x\n",
i,
reg_val);
}
for (i = AW210XX_REG_SL00; i <= sl_val; i++) {
if (!(aw210xx_reg_access[i] & REG_RD_ACCESS))
continue;
aw210xx_i2c_read(aw210xx, i, &reg_val);
len += snprintf(buf + len,
PAGE_SIZE - len,
"reg:0x%02x=0x%02x\n",
i,
reg_val);
}
for (i = AW210XX_REG_GCCR; i <= AW210XX_REG_GCFG; i++) {
if (!(aw210xx_reg_access[i] & REG_RD_ACCESS))
continue;
aw210xx_i2c_read(aw210xx, i, &reg_val);
len += snprintf(buf + len,
PAGE_SIZE - len,
"reg:0x%02x=0x%02x\n",
i,
reg_val);
}
return len;
}
static ssize_t aw210xx_hwen_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t len)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
int rc;
unsigned int val = 0;
rc = kstrtouint(buf, 0, &val);
if (rc < 0)
return rc;
if (val > 0)
aw210xx_hw_enable(aw210xx, true);
else
aw210xx_hw_enable(aw210xx, false);
return len;
}
static ssize_t
aw210xx_hwen_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
ssize_t len = 0;
len += snprintf(buf + len,
PAGE_SIZE - len,
"hwen=%d\n",
gpio_get_value(aw210xx->enable_gpio));
return len;
}
static ssize_t aw210xx_effect_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
ssize_t len = 0;
unsigned int i;
for (i = 0; i < (sizeof(aw210xx_cfg_array) / sizeof(aw210xx_cfg_t));
i++) {
len += snprintf(buf + len,
PAGE_SIZE - len,
"effect[%d]: %s\n",
i,
aw210xx_cfg_name[i]);
}
len += snprintf(buf + len,
PAGE_SIZE - len,
"current effect[%d]: %s\n",
aw210xx->effect,
aw210xx_cfg_name[aw210xx->effect]);
return len;
}
static ssize_t aw210xx_effect_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t len)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
int rc;
unsigned int val = 0;
rc = kstrtouint(buf, 10, &val);
if (rc < 0)
return rc;
if ((val >= (sizeof(aw210xx_cfg_array) / sizeof(aw210xx_cfg_t))) ||
(val < 0)) {
pr_err("%s, store effect num error.\n", __func__);
return -EINVAL;
}
aw210xx->effect = val;
pr_info("%s, line%d,val = %d\n", __func__, __LINE__, val);
aw210xx_cfg_update(aw210xx);
return len;
}
static void
aw210xx_rgb_write(struct aw210xx *aw210xx, uint8_t *rgb_data, uint8_t rgb_num)
{
uint8_t *rgb_ptr = rgb_data;
if (rgb_num >= aw210xx->num_leds) {
dev_err(aw210xx->dev,
"%s: Invalid rgb_num %d, valid range [0,%d]\n",
__func__,
rgb_num,
aw210xx->num_leds - 1);
return;
}
aw210xx_i2c_write_block(aw210xx,
AW210XX_REG_SL00 + rgb_num * 3,
3 /*len*/,
rgb_data);
}
static ssize_t aw210xx_rgb_read(struct aw210xx *aw210xx,
unsigned char *rgb_buf,
uint8_t rgb_num)
{
int i;
unsigned char rgb[3];
if (rgb_num >= aw210xx->num_leds) {
dev_err(aw210xx->dev,
"%s: Invalid rgb_num %d, valid range [0,%d]\n",
__func__,
rgb_num,
aw210xx->num_leds - 1);
return -EINVAL;
}
aw210xx_i2c_read_block(aw210xx,
AW210XX_REG_SL00 + rgb_num * 3,
3 /*len*/,
rgb);
return scnprintf(rgb_buf,
PAGE_SIZE,
"%hhu %hhu %hhu\n",
rgb[0],
rgb[1],
rgb[2]);
}
static ssize_t aw210xx_rgbcolor_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t len)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
uint32_t rgb_num = 0;
uint32_t rgb_data = 0;
uint8_t rgb[3];
if (sscanf(buf, "%x %x", &rgb_num, &rgb_data) == 2) {
rgb[0] = (rgb_data & 0xff0000) >> 16;
rgb[1] = (rgb_data & 0x00ff00) >> 8;
rgb[2] = (rgb_data & 0x0000ff);
aw210xx_rgb_write(aw210xx, rgb, (uint8_t)rgb_num);
/* br set */
aw210xx_i2c_write(aw210xx,
AW210XX_REG_BR00L + (uint8_t)rgb_num,
AW210XX_GLOBAL_DEFAULT_SET);
/* update */
aw210xx_update(aw210xx);
}
return len;
}
// TODO(rushikesh@) This API should be updated to restore any AW210xx config
// register settings it changes. It currently does make changes and doesn't
// cleanup after itself. Not a problem in production because we don't use this
// API for anything currently, but if it were used it could place the driver in
// a state unfriendly to userspace logic
static ssize_t aw210xx_singleled_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t len)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
uint32_t led_num = 0;
uint32_t rgb_data = 0;
uint32_t rgb_brightness = 0;
if (sscanf(buf, "%x %x %x", &led_num, &rgb_data, &rgb_brightness) ==
3) {
if (aw210xx->chipid == AW21018_CHIPID) {
if (led_num > AW21018_LED_NUM)
led_num = AW21018_LED_NUM;
} else if (aw210xx->chipid == AW21012_CHIPID) {
if (led_num > AW21012_LED_NUM)
led_num = AW21012_LED_NUM;
} else {
if (led_num > AW21009_LED_NUM)
led_num = AW21009_LED_NUM;
}
aw210xx_singleled_set(aw210xx,
led_num,
rgb_data,
rgb_brightness);
}
return len;
}
static ssize_t aw210xx_opdetect_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
ssize_t len = 0;
int i = 0;
uint8_t reg_val[3] = {0};
aw210xx_open_short_detect(aw210xx, AW210XX_OPEN_DETECT, reg_val);
for (i = 0; i < sizeof(reg_val); i++)
len += snprintf(buf + len,
PAGE_SIZE - len,
"OSST%d:%#x\n",
i,
reg_val[i]);
return len;
}
static ssize_t aw210xx_stdetect_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
ssize_t len = 0;
int i = 0;
uint8_t reg_val[3] = {0};
aw210xx_open_short_detect(aw210xx, AW210XX_SHORT_DETECT, reg_val);
for (i = 0; i < sizeof(reg_val); i++)
len += snprintf(buf + len,
PAGE_SIZE - len,
"OSST%d:%#x\n",
i,
reg_val[i]);
return len;
}
/*Functions below are to support the LP5018 compatability sysfs nodes*/
static ssize_t aw210xx_led_global_dimming_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
return scnprintf(buf, PAGE_SIZE, "%u\n", aw210xx->global_dimming);
}
__always_inline static void aw210xx_set_pwm_scale(struct aw210xx *aw210xx)
{
aw210xx->pwm_scale = MAX_PWM_SCALE * aw210xx->current_limit *
aw210xx->global_dimming /
(MAX_CURRENT_LIMIT_PERCENT * MAX_GLOBAL_DIMMING);
}
static ssize_t aw210xx_led_global_dimming_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
int global_dimming;
if (kstrtouint(buf, 10 /*base*/, &global_dimming))
return -EINVAL;
if (global_dimming > 255 || global_dimming < 0) {
dev_err(aw210xx->dev,
"%s: Invalid Global Dimming value %d, valid range [0,255]\n",
__func__,
global_dimming);
return -EINVAL;
}
aw210xx->global_dimming = global_dimming;
aw210xx_set_pwm_scale(aw210xx);
dev_info(aw210xx->dev,
"%s: Updated Global Dimming to %u\n",
__func__,
aw210xx->global_dimming);
return count;
}
// For LP5018, the start_num situation allows the LEDs to be out of order
// or offset from channel 0. However, in practice they're laid out
// in order starting from 0. Since this code will be used for
// prince/valens only, it's probably best to simplify this
// unecessary abstraction
static ssize_t aw210xx_led_pwm_all_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
int num_leds = 0;
int chars_read = 0;
int led_idx;
int ret;
int i;
int rgb_start_idx;
int min_rgb_start_idx = AW21018_MAX_LED_CHANNELS;
int max_rgb_start_idx = 0;
u8 rgb[AW21018_MAX_LED_CHANNELS];
int need_to_update = aw210xx->force_sync;
memcpy(&rgb[0], &aw210xx->saved_value[0], AW21018_MAX_LED_CHANNELS);
while (sscanf(buf, "%d%n", &led_idx, &chars_read) == 1) {
if (led_idx > aw210xx->num_leds - 1 || led_idx < 0) {
dev_err(aw210xx->dev,
"%s: incorrect led index %d supplied\n",
__func__,
led_idx);
return -EINVAL;
}
++num_leds;
if (num_leds > aw210xx->num_leds) {
dev_err(aw210xx->dev,
"%s: incorrect number of leds supplied\n",
__func__);
return -EINVAL;
}
buf += chars_read;
rgb_start_idx = led_idx * 3;
// Keep track of the minimum-sized contiguous block of data we
// need to write
if (rgb_start_idx < min_rgb_start_idx) {
min_rgb_start_idx = rgb_start_idx;
}
if (rgb_start_idx > max_rgb_start_idx) {
max_rgb_start_idx = rgb_start_idx;
}
if (sscanf(buf,
"%hhu %hhu %hhu%n",
&rgb[rgb_start_idx],
&rgb[rgb_start_idx + 1],
&rgb[rgb_start_idx + 2],
&chars_read) != 3) {
dev_err(aw210xx->dev,
"%s: unable to parse led values to write\n",
__func__);
return -EINVAL;
}
// Apply Current Limit
for (i = 0; i < 3; i++) {
rgb[rgb_start_idx + i] =
(u8)((u32)(rgb[rgb_start_idx + i]) *
aw210xx->pwm_scale / (u32)MAX_PWM_SCALE);
}
buf += chars_read;
if (!need_to_update &&
(rgb[rgb_start_idx] !=
aw210xx->saved_value[rgb_start_idx] ||
rgb[rgb_start_idx + 1] !=
aw210xx->saved_value[rgb_start_idx + 1] ||
rgb[rgb_start_idx + 2] !=
aw210xx->saved_value[rgb_start_idx + 2])) {
need_to_update = 1;
}
}
if (!need_to_update) {
return count;
}
// Because of force_sync, we may have an empty pwm_all string but need
// to write all the registers. Set the bounds to min and max in this
// case
if (min_rgb_start_idx == AW21018_MAX_LED_CHANNELS ||
max_rgb_start_idx == 0) {
min_rgb_start_idx = 0;
max_rgb_start_idx = AW21018_MAX_LED_CHANNELS - 3;
}
// Perform a minimum-size write to preserve as much existing LED state
// as possible (i.e. if any other LEDs are on, keep them on).
ret = aw210xx_i2c_write_block(aw210xx,
AW210XX_REG_SL00 + min_rgb_start_idx,
max_rgb_start_idx - min_rgb_start_idx + 3,
rgb + min_rgb_start_idx);
if (ret < 0) {
dev_err(aw210xx->dev,
"%s: failed to write rgb values to all leds, err: %d\n",
__func__,
ret);
return ret;
}
aw210xx_update(aw210xx);
memcpy(&aw210xx->saved_value[0], &rgb[0], aw210xx->num_leds * 3);
return count;
}
static ssize_t aw210xx_current_limit_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
return scnprintf(buf, PAGE_SIZE, "%u\n", aw210xx->current_limit);
}
static ssize_t aw210xx_current_limit_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
int current_limit;
if (kstrtouint(buf, 10, &current_limit))
return -EINVAL;
if (current_limit > MAX_CURRENT_LIMIT_PERCENT) {
dev_err(aw210xx->dev,
"%s: Invalid Current Limit %d\n",
__func__,
current_limit);
return -EINVAL;
}
dev_info(aw210xx->dev,
"%s: Updating Current Limit to %02hhd\n",
__func__,
current_limit);
aw210xx->current_limit = current_limit;
aw210xx_set_pwm_scale(aw210xx);
return count;
}
static ssize_t aw210xx_led_force_sync_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
unsigned char force_sync = (unsigned char)aw210xx->force_sync;
int ret;
ret = sscanf(buf, "%hhu", &force_sync);
if (ret == 1 && (force_sync == 0 || force_sync == 1)) {
dev_info(aw210xx->dev, "%s: force LED sync: %u\n", __func__, force_sync);
aw210xx->force_sync = force_sync;
} else {
dev_err(aw210xx->dev, "%s: invalid value: %u\n", __func__, force_sync);
}
return count;
}
static ssize_t aw210xx_led_pwm_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
unsigned int rgb_num;
int ret = sscanf(attr->attr.name, "pwm%u", &rgb_num);
if (ret != 1) {
dev_err(aw210xx->dev, "%s: failed to get led index.\n", __func__);
return -EINVAL;
}
return aw210xx_rgb_read(aw210xx, buf, rgb_num);
}
static ssize_t aw210xx_led_pwm_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
uint8_t rgb[3];
uint8_t *saved;
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
uint32_t rgb_num;
int i;
int ret = sscanf(attr->attr.name, "pwm%u", &rgb_num);
if (ret != 1) {
dev_err(aw210xx->dev, "%s: failed to get led index.\n", __func__);
return -EINVAL;
}
ret = sscanf(buf, "%hhu %hhu %hhu", &rgb[0], &rgb[1], &rgb[2]);
if (ret != 3) {
dev_err(aw210xx->dev,
"%s: failed to parse rgb values, ret=%d.\n",
__func__,
ret);
return -EINVAL;
}
for (i = 0; i < 3; i++) {
rgb[i] = (u8)((u32)(rgb[i]) * aw210xx->pwm_scale /
(u32)MAX_PWM_SCALE);
}
saved = &(aw210xx->saved_value[rgb_num * 3]);
if (!aw210xx->force_sync && saved[0] == rgb[0] && saved[1] == rgb[1] &&
saved[2] == rgb[2]) {
return count;
}
// Convert the u8 array to an int32 or accept a u8 array in place of an
// int32 - latter is better since block writes should use arrays
aw210xx_rgb_write(aw210xx, rgb, (uint8_t)rgb_num);
aw210xx_update(aw210xx);
// save the values we just wrote
memcpy(&(aw210xx->saved_value[rgb_num * 3]), rgb, sizeof(rgb));
return count;
}
static ssize_t aw210xx_led_iref_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
unsigned int rgb_num;
int ret = sscanf(attr->attr.name, "iref%u", &rgb_num);
unsigned char brightness;
if (ret != 1) {
dev_err(aw210xx->dev, "%s: failed to get led index.\n", __func__);
return -EINVAL;
}
aw210xx_i2c_read(aw210xx, AW210XX_REG_BR00L + rgb_num, &brightness);
return scnprintf(buf, PAGE_SIZE, "%hhu\n", brightness);
}
static ssize_t aw210xx_led_iref_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw210xx *aw210xx = container_of(led_cdev, struct aw210xx, cdev);
unsigned int rgb_num;
int ret = sscanf(attr->attr.name, "iref%u", &rgb_num);
uint8_t brightness;
if (ret != 1) {
dev_err(aw210xx->dev, "%s: failed to get led index.\n", __func__);
return -EINVAL;
}
ret = sscanf(buf, "%hhu", &brightness);
if (ret != 1) {
dev_err(aw210xx->dev,
"%s: failed to parse brightness, ret=%d.\n",
__func__,
ret);
return -EINVAL;
}
aw210xx_i2c_write(aw210xx,
AW210XX_REG_BR00L + rgb_num,
(uint8_t)brightness);
aw210xx_update(aw210xx);
}
static void delete_iref_pwm_attr_array(void)
{
memset(aw210xx_attr_names, 0, sizeof(aw210xx_attr_names));
memset(aw210xx_iref_pwm_attrs, 0, sizeof(aw210xx_iref_pwm_attrs));
return;
}
static void construct_iref_pwm_device_attributes(int num_leds,
struct kobject *kobj)
{
int i, error;
if (num_leds > AW21018_MAX_RGB_LED) {
return;
}
// data->all_attrs = aw210xx_all_attrs;
// data->iref_pwm_attrs = aw210xx_iref_pwm_attrs;
for (i = 0; i < 2 * num_leds; ++i) {
char buf[AW210XX_ATTR_NAME_LEN_MAX];
char *name;
if (i < num_leds)
snprintf(buf, sizeof(buf), "iref%u", i);
else
snprintf(buf, sizeof(buf), "pwm%u", i - num_leds);
name = aw210xx_attr_names[i];
strncpy(name, buf, AW210XX_ATTR_NAME_LEN_MAX);
aw210xx_iref_pwm_attrs[i].attr.name = name;
aw210xx_iref_pwm_attrs[i].attr.mode =
S_IWUSR | S_IWGRP | S_IRUGO;
if (i < num_leds) {
aw210xx_iref_pwm_attrs[i].show = aw210xx_led_iref_show;
aw210xx_iref_pwm_attrs[i].store =
aw210xx_led_iref_store;
} else {
aw210xx_iref_pwm_attrs[i].show = aw210xx_led_pwm_show;
aw210xx_iref_pwm_attrs[i].store = aw210xx_led_pwm_store;
}
all_generated_attrs[i] = &aw210xx_iref_pwm_attrs[i].attr;
}
iref_pwm_attr_group.attrs = all_generated_attrs;
error = sysfs_create_group(kobj, &iref_pwm_attr_group);
if (error)
printk("%s: failed to create sysfs, err:%d\n", __func__, error);
return;
}
static DEVICE_ATTR(reg, 0664, aw210xx_reg_show, aw210xx_reg_store);
static DEVICE_ATTR(hwen, 0664, aw210xx_hwen_show, aw210xx_hwen_store);
static DEVICE_ATTR(effect, 0664, aw210xx_effect_show, aw210xx_effect_store);
static DEVICE_ATTR(rgbcolor, 0664, NULL, aw210xx_rgbcolor_store);
static DEVICE_ATTR(singleled, 0664, NULL, aw210xx_singleled_store);
static DEVICE_ATTR(opdetect, 0664, aw210xx_opdetect_show, NULL);
static DEVICE_ATTR(stdetect, 0664, aw210xx_stdetect_show, NULL);
/*The following sysfs nodes are implmented to match lp5018 behavior*/
static DEVICE_ATTR(global_dimming,
S_IWUSR | S_IWGRP | S_IRUGO,
aw210xx_led_global_dimming_show,
aw210xx_led_global_dimming_store);
static DEVICE_ATTR(pwm_all,
S_IWUSR | S_IWGRP | S_IRUGO,
NULL,
aw210xx_led_pwm_all_store);
static DEVICE_ATTR(current_limit,
S_IWUSR | S_IWGRP | S_IRUGO,
aw210xx_current_limit_show,
aw210xx_current_limit_store);
static DEVICE_ATTR(force_sync,
S_IWUSR | S_IWGRP | S_IRUGO,
NULL,
aw210xx_led_force_sync_store);
static struct attribute *aw210xx_attributes[] = {
&dev_attr_reg.attr,
&dev_attr_hwen.attr,
&dev_attr_effect.attr,
&dev_attr_rgbcolor.attr,
&dev_attr_singleled.attr,
&dev_attr_opdetect.attr,
&dev_attr_stdetect.attr,
&dev_attr_global_dimming.attr,
&dev_attr_pwm_all.attr,
&dev_attr_current_limit.attr,
&dev_attr_force_sync.attr,
NULL,
};
static struct attribute_group aw210xx_attribute_group = {
.attrs = aw210xx_attributes};
/******************************************************
*
* led class dev
******************************************************/
static int aw210xx_parse_led_cdev(struct aw210xx *aw210xx,
struct device_node *np)
{
int ret = -1;
struct device_node *temp;
AW_LOG("enter\n");
for_each_child_of_node(np, temp)
{
ret = of_property_read_string(temp,
"aw210xx,name",
&aw210xx->cdev.name);
if (ret < 0) {
dev_err(aw210xx->dev,
"Failure reading led name, ret = %d\n",
ret);
goto free_pdata;
}
ret = of_property_read_u32(temp,
"aw210xx,imax",
&aw210xx->imax);
if (ret < 0) {
dev_err(aw210xx->dev,
"Failure reading imax, ret = %d\n",
ret);
goto free_pdata;
}
ret = of_property_read_u32(temp,
"aw210xx,brightness",
&aw210xx->cdev.brightness);
if (ret < 0) {
dev_err(aw210xx->dev,
"Failure reading brightness, ret = %d\n",
ret);
goto free_pdata;
}
ret = of_property_read_u32(temp,
"aw210xx,max_brightness",
&aw210xx->cdev.max_brightness);
if (ret < 0) {
dev_err(aw210xx->dev,
"Failure reading max brightness, ret = %d\n",
ret);
goto free_pdata;
}
ret = of_property_read_u32(temp,
"aw210xx,num_leds",
&aw210xx->num_leds);
if (ret < 0) {
dev_err(aw210xx->dev,
"Failure reading num leds, ret = %d\n",
ret);
goto free_pdata;
}
printk("aw210xx num_leds=%d\n", aw210xx->num_leds);
ret = of_property_read_u32(
temp,
"aw210xx,default_current_limit_percent",
&aw210xx->current_limit);
if (ret < 0) {
dev_err(aw210xx->dev,
"No default current limit specified in dts.\n");
aw210xx->current_limit = MAX_CURRENT_LIMIT_PERCENT;
}
if (aw210xx->current_limit > MAX_CURRENT_LIMIT_PERCENT) {
dev_err(aw210xx->dev,
"No default current limit specified in dts.\n");
aw210xx->current_limit = MAX_CURRENT_LIMIT_PERCENT;
}
printk("aw210xx current_limit=%d\n", aw210xx->current_limit);
}
INIT_WORK(&aw210xx->brightness_work, aw210xx_brightness_work);
aw210xx->cdev.brightness_set = aw210xx_set_brightness;
ret = led_classdev_register(aw210xx->dev, &aw210xx->cdev);
if (ret) {
AW_ERR("unable to register led ret=%d\n", ret);
goto free_pdata;
}
ret = sysfs_create_group(&aw210xx->cdev.dev->kobj,
&aw210xx_attribute_group);
if (ret) {
AW_ERR("led sysfs ret: %d\n", ret);
goto free_class;
}
aw210xx_led_init(aw210xx);
return 0;
free_class:
led_classdev_unregister(&aw210xx->cdev);
free_pdata:
return ret;
}
/*****************************************************
*
* check chip id and version
*
*****************************************************/
static int aw210xx_read_chipid(struct aw210xx *aw210xx)
{
int ret = -1;
unsigned char cnt = 0;
unsigned char chipid = 0;
while (cnt < AW_READ_CHIPID_RETRIES) {
ret = aw210xx_i2c_read(aw210xx, AW210XX_REG_RESET, &chipid);
if (ret < 0) {
AW_ERR("failed to read chipid: %d\n", ret);
} else {
aw210xx->chipid = chipid;
switch (aw210xx->chipid) {
case AW21018_CHIPID:
AW_LOG("AW21018, read chipid = 0x%02x!!\n",
chipid);
return 0;
case AW21012_CHIPID:
AW_LOG("AW21012, read chipid = 0x%02x!!\n",
chipid);
return 0;
case AW21009_CHIPID:
AW_LOG("AW21009, read chipid = 0x%02x!!\n",
chipid);
return 0;
default:
AW_LOG("chip is unsupported device id = %x\n",
chipid);
break;
}
}
cnt++;
usleep_range(AW_READ_CHIPID_RETRY_DELAY * 1000,
AW_READ_CHIPID_RETRY_DELAY * 1000 + 500);
}
return -EINVAL;
}
/*****************************************************
*
* device tree
*
*****************************************************/
static int aw210xx_parse_dt(struct device *dev,
struct aw210xx *aw210xx,
struct device_node *np)
{
int ret = -EINVAL;
aw210xx->enable_gpio = of_get_named_gpio(np, "enable-gpio", 0);
if (aw210xx->enable_gpio < 0) {
aw210xx->enable_gpio = -1;
AW_ERR("no enable gpio provided, HW enable unsupported\n");
return ret;
}
ret = of_property_read_u32(np, "osc_clk", &aw210xx->osc_clk);
if (ret < 0) {
AW_ERR("no osc_clk provided, osc clk unsupported\n");
return ret;
}
ret = of_property_read_u32(np, "br_res", &aw210xx->br_res);
if (ret < 0) {
AW_ERR("brightness resolution unsupported\n");
return ret;
}
ret = of_property_read_u32(np, "pde", &aw210xx->pde);
if (ret < 0) {
AW_ERR("phase delay enable unsupported\n");
return ret;
}
ret = of_property_read_u32(np, "global_current", &aw210xx->glo_current);
if (ret < 0) {
AW_ERR("global current resolution unsupported\n");
return ret;
}
return 0;
}
/******************************************************
*
* i2c driver
*
******************************************************/
static int aw210xx_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct aw210xx *aw210xx;
struct device_node *np = i2c->dev.of_node;
int ret;
AW_LOG("enter\n");
if (skip == 1)
return -ENODEV;
if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C)) {
AW_ERR("check_functionality failed\n");
return -EIO;
}
aw210xx = devm_kzalloc(&i2c->dev, sizeof(struct aw210xx), GFP_KERNEL);
if (aw210xx == NULL)
return -ENOMEM;
aw210xx->dev = &i2c->dev;
aw210xx->i2c = i2c;
i2c_set_clientdata(i2c, aw210xx);
/* aw210xx parse device tree */
if (np) {
ret = aw210xx_parse_dt(&i2c->dev, aw210xx, np);
if (ret) {
AW_ERR("failed to parse device tree node\n");
goto err_parse_dt;
}
}
/* hardware enable */
aw210xx_hw_enable(aw210xx, true);
/* aw210xx identify */
ret = aw210xx_read_chipid(aw210xx);
if (ret < 0) {
AW_ERR("aw210xx_read_chipid failed ret=%d\n", ret);
goto err_id;
}
dev_set_drvdata(&i2c->dev, aw210xx);
aw210xx_parse_led_cdev(aw210xx, np);
if (ret < 0) {
AW_ERR("error creating led class dev\n");
goto err_sysfs;
}
construct_iref_pwm_device_attributes(aw210xx->num_leds,
&aw210xx->cdev.dev->kobj);
aw210xx->global_dimming = MAX_GLOBAL_DIMMING;
aw210xx_set_pwm_scale(aw210xx);
AW_LOG("probe completed!\n");
return 0;
err_sysfs:
delete_iref_pwm_attr_array();
err_id:
err_parse_dt:
devm_kfree(&i2c->dev, aw210xx);
aw210xx = NULL;
return ret;
}
static int aw210xx_i2c_remove(struct i2c_client *i2c)
{
struct aw210xx *aw210xx = i2c_get_clientdata(i2c);
AW_LOG("enter\n");
sysfs_remove_group(&aw210xx->cdev.dev->kobj, &aw210xx_attribute_group);
sysfs_remove_group(&aw210xx->cdev.dev->kobj, &iref_pwm_attr_group);
delete_iref_pwm_attr_array();
led_classdev_unregister(&aw210xx->cdev);
devm_kfree(&i2c->dev, aw210xx);
aw210xx = NULL;
return 0;
}
static const struct i2c_device_id aw210xx_i2c_id[] = {{AW210XX_I2C_NAME, 0},
{}};
MODULE_DEVICE_TABLE(i2c, aw210xx_i2c_id);
// clang-format off
static const struct of_device_id aw210xx_dt_match[] = {
{.compatible = "awinic,aw210xx_led"},
{}
};
static struct i2c_driver aw210xx_i2c_driver = {
.driver = {
.name = AW210XX_I2C_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(aw210xx_dt_match),
},
.probe = aw210xx_i2c_probe,
.remove = aw210xx_i2c_remove,
.id_table = aw210xx_i2c_id,
};
// clang-format on
static int __init aw210xx_i2c_init(void)
{
int ret = 0;
AW_LOG("enter, aw210xx driver version %s\n", AW210XX_DRIVER_VERSION);
ret = i2c_add_driver(&aw210xx_i2c_driver);
if (ret) {
AW_ERR("failed to register aw210xx driver!\n");
return ret;
}
return 0;
}
module_init(aw210xx_i2c_init);
static void __exit aw210xx_i2c_exit(void)
{
i2c_del_driver(&aw210xx_i2c_driver);
}
module_exit(aw210xx_i2c_exit);
MODULE_DESCRIPTION("AW210XX LED Driver");
MODULE_LICENSE("GPL v2");