| /* |
| * 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, ®_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, ®_val[0]); |
| aw210xx_i2c_read(aw210xx, AW210XX_REG_OSST1, ®_val[1]); |
| aw210xx_i2c_read(aw210xx, AW210XX_REG_OSST2, ®_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, ®_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, ®_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, ®_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, ®_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, ¤t_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"); |