780 lines
22 KiB
C
780 lines
22 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
|
|
*/
|
|
|
|
#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-tlc59116.h"
|
|
|
|
#define MESON_TLC59116_I2C_NAME "tlc59116_led"
|
|
#define MESON_LEDS_CDEV_NAME "i2c_leds"
|
|
#define MESON_TLC59116_VERSION "v2.0.0"
|
|
|
|
#define MESON_TLC59116_I2C_RETRIES 3
|
|
#define MESON_TLC59116_I2C_RETRY_DELAY 1
|
|
#define MESON_TLC59116_COLORS_COUNT 3
|
|
#define MESON_TLC59116_MAX_IO 16
|
|
#define MESON_TLC59116_BRI_MAX 0xFF
|
|
|
|
/* tlc59116 register addr */
|
|
#define MESON_TLC59116_REG_MODE_1 0x00
|
|
#define MESON_TLC59116_REG_MODE_2 0x01
|
|
#define MESON_TLC59116_REG_PWM_0 0x02
|
|
#define MESON_TLC59116_REG_PWM_1 0x03
|
|
#define MESON_TLC59116_REG_PWM_2 0x04
|
|
#define MESON_TLC59116_REG_PWM_3 0x05
|
|
#define MESON_TLC59116_REG_PWM_4 0x06
|
|
#define MESON_TLC59116_REG_PWM_5 0x07
|
|
#define MESON_TLC59116_REG_PWM_6 0x08
|
|
#define MESON_TLC59116_REG_PWM_7 0x09
|
|
#define MESON_TLC59116_REG_PWM_8 0x0a
|
|
#define MESON_TLC59116_REG_PWM_9 0x0b
|
|
#define MESON_TLC59116_REG_PWM_10 0x0c
|
|
#define MESON_TLC59116_REG_PWM_11 0x0d
|
|
#define MESON_TLC59116_REG_PWM_12 0x0e
|
|
#define MESON_TLC59116_REG_PWM_13 0x0f
|
|
#define MESON_TLC59116_REG_PWM_14 0x10
|
|
#define MESON_TLC59116_REG_PWM_15 0x11
|
|
#define MESON_TLC59116_REG_GRPPWM 0x12
|
|
#define MESON_TLC59116_REG_GRPFREQ 0x13
|
|
#define MESON_TLC59116_REG_LEDOUT0 0x14
|
|
#define MESON_TLC59116_REG_LEDOUT1 0x15
|
|
#define MESON_TLC59116_REG_LEDOUT2 0x16
|
|
#define MESON_TLC59116_REG_LEDOUT3 0x17
|
|
#define MESON_TLC59116_REG_SUBADR1 0x18
|
|
#define MESON_TLC59116_REG_SUBADR2 0x19
|
|
#define MESON_TLC59116_REG_SUBADR3 0x1a
|
|
#define MESON_TLC59116_REG_ALLCALLADR 0x1b
|
|
#define MESON_TLC59116_REG_IREF 0x1c
|
|
#define MESON_TLC59116_REG_EFLAG1 0x1d
|
|
#define MESON_TLC59116_REG_EFLAG2 0x1e
|
|
#define MESON_TLC59116_REG_MAX 0x1f
|
|
|
|
/* tlc59116 register read/write access*/
|
|
#define MESON_TLC59116_REG_NONE_ACCESS 0
|
|
#define MESON_TLC59116_REG_RD_ACCESS BIT(0)
|
|
#define MESON_TLC59116_REG_WR_ACCESS BIT(1)
|
|
#define MESON_TLC59116_REG_ALL_ACCESS (BIT(0) | BIT(1))
|
|
|
|
static int meson_tlc59116_debug;
|
|
static DEFINE_MUTEX(meson_tlc59116_lock);
|
|
|
|
const unsigned char meson_tlc59116_reg_access[MESON_TLC59116_REG_MAX] = {
|
|
[MESON_TLC59116_REG_MODE_1] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_MODE_2] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_PWM_0] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_PWM_1] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_PWM_2] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_PWM_3] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_PWM_4] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_PWM_5] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_PWM_6] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_PWM_7] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_PWM_8] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_PWM_9] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_PWM_10] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_PWM_11] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_PWM_12] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_PWM_13] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_PWM_14] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_PWM_15] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_GRPPWM] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_GRPFREQ] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_LEDOUT0] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_LEDOUT1] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_LEDOUT2] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_LEDOUT3] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_SUBADR1] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_SUBADR2] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_SUBADR3] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_ALLCALLADR] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_IREF] = MESON_TLC59116_REG_ALL_ACCESS,
|
|
[MESON_TLC59116_REG_EFLAG1] = MESON_TLC59116_REG_RD_ACCESS,
|
|
[MESON_TLC59116_REG_EFLAG2] = MESON_TLC59116_REG_RD_ACCESS,
|
|
};
|
|
|
|
static int meson_tlc59116_i2c_writes(struct i2c_client *client,
|
|
unsigned char sreg_addr,
|
|
u8 length, u8 *bufs)
|
|
{
|
|
struct i2c_msg msg;
|
|
int i, ret;
|
|
u8 data[64];
|
|
|
|
if (length >= MESON_TLC59116_MAX_IO)
|
|
length = MESON_TLC59116_MAX_IO;
|
|
|
|
data[0] = sreg_addr;
|
|
for (i = 1; i <= length; i++)
|
|
data[i] = bufs[i - 1];
|
|
|
|
msg.addr = client->addr;
|
|
msg.flags = !I2C_M_RD;
|
|
msg.len = length + 1;
|
|
msg.buf = data;
|
|
|
|
ret = i2c_transfer(client->adapter, &msg, 1);
|
|
if (ret < 0) {
|
|
pr_debug("[tlc59116_i2c_writes]error...\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int meson_tlc59116_i2c_write(struct meson_tlc59116 *tlc59116,
|
|
unsigned char reg_addr,
|
|
unsigned char reg_data)
|
|
{
|
|
int ret = -1;
|
|
unsigned char cnt = 0;
|
|
|
|
while (cnt < MESON_TLC59116_I2C_RETRIES) {
|
|
ret = i2c_smbus_write_byte_data(tlc59116->i2c,
|
|
reg_addr,
|
|
reg_data);
|
|
if (ret < 0)
|
|
pr_debug("%s: i2c_write cnt = %d error = %d\n",
|
|
__func__, cnt, ret);
|
|
else
|
|
break;
|
|
|
|
cnt++;
|
|
msleep(MESON_TLC59116_I2C_RETRY_DELAY);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int meson_tlc59116_i2c_read(struct meson_tlc59116 *tlc59116,
|
|
unsigned char reg_addr,
|
|
unsigned char *reg_data)
|
|
{
|
|
int ret = -1;
|
|
unsigned char cnt = 0;
|
|
|
|
while (cnt < MESON_TLC59116_I2C_RETRIES) {
|
|
ret = i2c_smbus_read_byte_data(tlc59116->i2c, reg_addr);
|
|
if (ret < 0) {
|
|
pr_debug("%s: i2c_read cnt = %d error = %d\n",
|
|
__func__, cnt, ret);
|
|
} else {
|
|
*reg_data = ret;
|
|
break;
|
|
}
|
|
|
|
cnt++;
|
|
msleep(MESON_TLC59116_I2C_RETRY_DELAY);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int check_tlc59116_valid(struct meson_tlc59116 *tlc59116)
|
|
{
|
|
int ret = -1;
|
|
unsigned char reg_data = 0;
|
|
|
|
ret = meson_tlc59116_i2c_read(tlc59116, MESON_TLC59116_REG_MODE_1, ®_data);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int meson_tlc59116_set_colors(struct meson_tlc59116 *tlc59116)
|
|
{
|
|
struct meson_tlc59116_colors *color;
|
|
struct meson_tlc59116_io *io;
|
|
u8 color_data[MESON_TLC59116_MAX_IO];
|
|
unsigned int i;
|
|
|
|
if (meson_tlc59116_debug) {
|
|
pr_debug("tlc59116 set colors: 0x%x 0x%x 0x%x 0x%x\n",
|
|
tlc59116->colors_buf[0], tlc59116->colors_buf[1],
|
|
tlc59116->colors_buf[2], tlc59116->colors_buf[3]);
|
|
}
|
|
|
|
memset(color_data, 0, sizeof(color_data));
|
|
for (i = 0; i < tlc59116->led_counts; i++) {
|
|
io = &tlc59116->io[i];
|
|
color = &tlc59116->colors[i];
|
|
color->blue = tlc59116->colors_buf[i] & 0xff;
|
|
color_data[io->b_io] = color->blue;
|
|
color->green = (tlc59116->colors_buf[i] >> 8) & 0xff;
|
|
color_data[io->g_io] = color->green;
|
|
color->red = (tlc59116->colors_buf[i] >> 16) & 0xff;
|
|
color_data[io->r_io] = color->red;
|
|
}
|
|
|
|
return meson_tlc59116_i2c_writes(tlc59116->i2c,
|
|
(MESON_TLC59116_REG_PWM_0
|
|
| (0x5 << 5)), MESON_TLC59116_MAX_IO,
|
|
color_data);
|
|
}
|
|
|
|
static ssize_t colors_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
|
struct meson_tlc59116 *tlc59116 = container_of(led_cdev,
|
|
struct meson_tlc59116,
|
|
cdev);
|
|
int ret;
|
|
|
|
mutex_lock(&meson_tlc59116_lock);
|
|
ret = sscanf(buf, "%x %x %x %x", &tlc59116->colors_buf[0],
|
|
&tlc59116->colors_buf[1], &tlc59116->colors_buf[2],
|
|
&tlc59116->colors_buf[3]);
|
|
if (ret != 4) {
|
|
pr_debug(" enter,Line:...set led colors fail!\n");
|
|
return count;
|
|
}
|
|
|
|
meson_tlc59116_set_colors(tlc59116);
|
|
mutex_unlock(&meson_tlc59116_lock);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t colors_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
|
struct meson_tlc59116 *tlc59116 = container_of(led_cdev,
|
|
struct meson_tlc59116,
|
|
cdev);
|
|
int i;
|
|
|
|
for (i = 0; i < tlc59116->led_counts; i++) {
|
|
tlc59116->colors_buf[i] |= tlc59116->colors[i].red << 16;
|
|
tlc59116->colors_buf[i] |= tlc59116->colors[i].green << 8;
|
|
tlc59116->colors_buf[i] |= tlc59116->colors[i].blue;
|
|
}
|
|
|
|
return sprintf(buf, "0x%x 0x%x 0x%x 0x%x\n", tlc59116->colors_buf[0],
|
|
tlc59116->colors_buf[1], tlc59116->colors_buf[2],
|
|
tlc59116->colors_buf[3]);
|
|
}
|
|
|
|
static ssize_t reg_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
|
struct meson_tlc59116 *tlc59116 = container_of(led_cdev,
|
|
struct meson_tlc59116,
|
|
cdev);
|
|
|
|
unsigned int databuf[2] = {0, 0};
|
|
|
|
if (sscanf(buf, "%x %x", &databuf[0], &databuf[1]) == 2) {
|
|
if (databuf[0] >= MESON_TLC59116_REG_MAX) {
|
|
dev_err(tlc59116->dev, "%s error max register addr = 0x%x\n",
|
|
__func__, MESON_TLC59116_REG_MAX);
|
|
return count;
|
|
}
|
|
|
|
if (!(meson_tlc59116_reg_access[databuf[0]] &
|
|
MESON_TLC59116_REG_WR_ACCESS)) {
|
|
dev_err(tlc59116->dev, "%s error register addr = 0x%x is read only!\n",
|
|
__func__, databuf[0]);
|
|
return count;
|
|
}
|
|
|
|
meson_tlc59116_i2c_write(tlc59116, (unsigned char)databuf[0],
|
|
(unsigned char)databuf[1]);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static ssize_t reg_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
|
struct meson_tlc59116 *tlc59116 = container_of(led_cdev,
|
|
struct meson_tlc59116,
|
|
cdev);
|
|
ssize_t len = 0;
|
|
unsigned char i = 0;
|
|
unsigned char reg_val = 0;
|
|
|
|
for (i = 0; i < MESON_TLC59116_REG_MAX; i++) {
|
|
if (!(meson_tlc59116_reg_access[i] &
|
|
MESON_TLC59116_REG_RD_ACCESS))
|
|
continue;
|
|
|
|
meson_tlc59116_i2c_read(tlc59116, i, ®_val);
|
|
len += snprintf(buf + len, PAGE_SIZE - len,
|
|
"reg:0x%02x=0x%02x\n", i, reg_val);
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
static ssize_t debug_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
unsigned int debug = 0;
|
|
|
|
if (!kstrtoint(buf, 10, &debug)) {
|
|
meson_tlc59116_debug = debug;
|
|
pr_info("meson tlc59116 debug %s\n",
|
|
meson_tlc59116_debug ? "on" : "off");
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t debug_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
return sprintf(buf, "%d\n", meson_tlc59116_debug);
|
|
}
|
|
|
|
static ssize_t meson_tlc59116_io_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
|
struct meson_tlc59116 *tlc59116 = container_of(led_cdev,
|
|
struct meson_tlc59116,
|
|
cdev);
|
|
ssize_t len = 0;
|
|
int i = 0;
|
|
|
|
for (i = 0; i < tlc59116->led_counts; i++)
|
|
len += snprintf(buf + len, PAGE_SIZE - len,
|
|
"led[%d] R: %d G: %d B: %d\n", i,
|
|
tlc59116->io[i].r_io, tlc59116->io[i].g_io,
|
|
tlc59116->io[i].b_io);
|
|
|
|
return len;
|
|
}
|
|
|
|
static int meson_tlc59116_set_singlecolors(u32 ledid, struct meson_tlc59116 *tlc59116)
|
|
{
|
|
struct meson_tlc59116_colors *color;
|
|
struct meson_tlc59116_io *io;
|
|
u8 color_data[MESON_TLC59116_MAX_IO];
|
|
|
|
if (ledid > 3) {
|
|
pr_debug(" Exceed the maximum number of leds!\n");
|
|
return 0;
|
|
}
|
|
|
|
if (meson_tlc59116_debug) {
|
|
pr_debug("tlc59116 set colors: 0x%x\n",
|
|
tlc59116->colors_buf[0]);
|
|
}
|
|
|
|
memset(color_data, 0, sizeof(color_data));
|
|
|
|
switch (ledid) {
|
|
case LED_NUM0:
|
|
io = &tlc59116->io[0];
|
|
color = &tlc59116->colors[0];
|
|
color->blue = tlc59116->colors_buf[0] & 0xff;
|
|
color_data[io->b_io] = color->blue;
|
|
color->green = (tlc59116->colors_buf[0] >> 8) & 0xff;
|
|
color_data[io->g_io] = color->green;
|
|
color->red = (tlc59116->colors_buf[0] >> 16) & 0xff;
|
|
color_data[io->r_io] = color->red;
|
|
meson_tlc59116_i2c_write(tlc59116, tlc59116->io[0].b_io + REGISTER_OFFSET,
|
|
color_data[io->b_io]);
|
|
meson_tlc59116_i2c_write(tlc59116, tlc59116->io[0].g_io + REGISTER_OFFSET,
|
|
color_data[io->g_io]);
|
|
meson_tlc59116_i2c_write(tlc59116, tlc59116->io[0].r_io + REGISTER_OFFSET,
|
|
color_data[io->r_io]);
|
|
break;
|
|
|
|
case LED_NUM1:
|
|
io = &tlc59116->io[1];
|
|
color = &tlc59116->colors[1];
|
|
color->blue = tlc59116->colors_buf[0] & 0xff;
|
|
color_data[io->b_io] = color->blue;
|
|
color->green = (tlc59116->colors_buf[0] >> 8) & 0xff;
|
|
color_data[io->g_io] = color->green;
|
|
color->red = (tlc59116->colors_buf[0] >> 16) & 0xff;
|
|
color_data[io->r_io] = color->red;
|
|
meson_tlc59116_i2c_write(tlc59116, tlc59116->io[1].b_io + REGISTER_OFFSET,
|
|
color_data[io->b_io]);
|
|
meson_tlc59116_i2c_write(tlc59116, tlc59116->io[1].g_io + REGISTER_OFFSET,
|
|
color_data[io->g_io]);
|
|
meson_tlc59116_i2c_write(tlc59116, tlc59116->io[1].r_io + REGISTER_OFFSET,
|
|
color_data[io->r_io]);
|
|
break;
|
|
|
|
case LED_NUM2:
|
|
io = &tlc59116->io[2];
|
|
color = &tlc59116->colors[2];
|
|
color->blue = tlc59116->colors_buf[0] & 0xff;
|
|
color_data[io->b_io] = color->blue;
|
|
color->green = (tlc59116->colors_buf[0] >> 8) & 0xff;
|
|
color_data[io->g_io] = color->green;
|
|
color->red = (tlc59116->colors_buf[0] >> 16) & 0xff;
|
|
color_data[io->r_io] = color->red;
|
|
meson_tlc59116_i2c_write(tlc59116, tlc59116->io[2].b_io + REGISTER_OFFSET,
|
|
color_data[io->b_io]);
|
|
meson_tlc59116_i2c_write(tlc59116, tlc59116->io[2].g_io + REGISTER_OFFSET,
|
|
color_data[io->g_io]);
|
|
meson_tlc59116_i2c_write(tlc59116, tlc59116->io[2].r_io + REGISTER_OFFSET,
|
|
color_data[io->r_io]);
|
|
break;
|
|
|
|
case LED_NUM3:
|
|
io = &tlc59116->io[3];
|
|
color = &tlc59116->colors[3];
|
|
color->blue = tlc59116->colors_buf[0] & 0xff;
|
|
color_data[io->b_io] = color->blue;
|
|
color->green = (tlc59116->colors_buf[0] >> 8) & 0xff;
|
|
color_data[io->g_io] = color->green;
|
|
color->red = (tlc59116->colors_buf[0] >> 16) & 0xff;
|
|
color_data[io->r_io] = color->red;
|
|
meson_tlc59116_i2c_write(tlc59116, tlc59116->io[3].b_io + REGISTER_OFFSET,
|
|
color_data[io->b_io]);
|
|
meson_tlc59116_i2c_write(tlc59116, tlc59116->io[3].g_io + REGISTER_OFFSET,
|
|
color_data[io->g_io]);
|
|
meson_tlc59116_i2c_write(tlc59116, tlc59116->io[3].r_io + REGISTER_OFFSET,
|
|
color_data[io->r_io]);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t single_colors_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
u32 ledid;
|
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
|
struct meson_tlc59116 *tlc59116 = container_of(led_cdev, struct meson_tlc59116, cdev);
|
|
int ret;
|
|
|
|
mutex_lock(&meson_tlc59116_lock);
|
|
|
|
ret = sscanf(buf, "%d %x", &ledid, &tlc59116->colors_buf[0]);
|
|
if (ret != 2) {
|
|
dev_err(dev, "enter,Line:...set led colors fail!\n");
|
|
return count;
|
|
}
|
|
meson_tlc59116_set_singlecolors(ledid, tlc59116);
|
|
mutex_unlock(&meson_tlc59116_lock);
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR_RW(colors);
|
|
static DEVICE_ATTR_RW(reg);
|
|
static DEVICE_ATTR_RW(debug);
|
|
static DEVICE_ATTR_RO(meson_tlc59116_io);
|
|
static DEVICE_ATTR_WO(single_colors);
|
|
|
|
static struct attribute *tlc59116_attributes[] = {
|
|
&dev_attr_colors.attr,
|
|
&dev_attr_reg.attr,
|
|
&dev_attr_debug.attr,
|
|
&dev_attr_meson_tlc59116_io.attr,
|
|
&dev_attr_single_colors.attr,
|
|
NULL
|
|
};
|
|
|
|
static struct attribute_group tlc59116_attribute_group = {
|
|
.attrs = tlc59116_attributes
|
|
};
|
|
|
|
static int meson_tlc59116_init(struct meson_tlc59116 *tlc59116)
|
|
{
|
|
int i;
|
|
|
|
meson_tlc59116_i2c_write(tlc59116, MESON_TLC59116_REG_MODE_1, 0x00);
|
|
meson_tlc59116_i2c_write(tlc59116, MESON_TLC59116_REG_MODE_2, 0x00);
|
|
meson_tlc59116_i2c_write(tlc59116, MESON_TLC59116_REG_LEDOUT0, 0xaa);
|
|
meson_tlc59116_i2c_write(tlc59116, MESON_TLC59116_REG_LEDOUT1, 0xaa);
|
|
meson_tlc59116_i2c_write(tlc59116, MESON_TLC59116_REG_LEDOUT2, 0xaa);
|
|
meson_tlc59116_i2c_write(tlc59116, MESON_TLC59116_REG_LEDOUT3, 0xaa);
|
|
|
|
for (i = 0; i < tlc59116->led_counts; i++) {
|
|
tlc59116->colors_buf[i] |= tlc59116->colors[i].red << 16;
|
|
tlc59116->colors_buf[i] |= tlc59116->colors[i].green << 8;
|
|
tlc59116->colors_buf[i] |= tlc59116->colors[i].blue;
|
|
}
|
|
|
|
return meson_tlc59116_set_colors(tlc59116);
|
|
}
|
|
|
|
static int meson_tlc59116_check(struct meson_tlc59116 *tlc59116)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < tlc59116->led_counts; i++)
|
|
if (tlc59116->io[i].r_io >= MESON_TLC59116_MAX_IO ||
|
|
tlc59116->io[i].g_io >= MESON_TLC59116_MAX_IO ||
|
|
tlc59116->io[i].b_io >= MESON_TLC59116_MAX_IO ||
|
|
tlc59116->colors[i].red > MESON_TLC59116_BRI_MAX ||
|
|
tlc59116->colors[i].green > MESON_TLC59116_BRI_MAX ||
|
|
tlc59116->colors[i].blue > MESON_TLC59116_BRI_MAX)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int meson_tlc59116_init_data(struct meson_tlc59116 *tlc59116)
|
|
{
|
|
int ret;
|
|
|
|
ret = meson_tlc59116_check(tlc59116);
|
|
if (ret) {
|
|
dev_err(tlc59116->dev, "%s error tlc59116 check data fail!\n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
|
|
return meson_tlc59116_init(tlc59116);
|
|
}
|
|
|
|
static void tlc59116_set_brightness(struct led_classdev *cdev,
|
|
enum led_brightness brightness)
|
|
{
|
|
struct meson_tlc59116 *tlc59116 = container_of(cdev,
|
|
struct meson_tlc59116,
|
|
cdev);
|
|
|
|
tlc59116->cdev.brightness = brightness;
|
|
}
|
|
|
|
static int meson_tlc59116_parse_led_dt(struct meson_tlc59116 *tlc59116,
|
|
struct device_node *np)
|
|
{
|
|
struct device_node *temp;
|
|
int ret = -1;
|
|
int i = 0;
|
|
|
|
if (of_property_read_bool(np, "ignore-led-suspend"))
|
|
tlc59116->ignore_led_suspend = 1;
|
|
|
|
for_each_child_of_node(np, temp) {
|
|
ret = of_property_read_u32_array(temp, "default_colors",
|
|
(unsigned int *)
|
|
(tlc59116->colors + i),
|
|
MESON_TLC59116_COLORS_COUNT);
|
|
if (ret < 0) {
|
|
dev_err(tlc59116->dev,
|
|
"Failure reading default colors ret = %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = of_property_read_u32(temp, "r_io_number",
|
|
&tlc59116->io[i].r_io);
|
|
if (ret < 0) {
|
|
dev_err(tlc59116->dev,
|
|
"Failure reading imax ret = %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = of_property_read_u32(temp, "g_io_number",
|
|
&tlc59116->io[i].g_io);
|
|
if (ret < 0) {
|
|
dev_err(tlc59116->dev,
|
|
"Failure reading brightness ret = %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = of_property_read_u32(temp, "b_io_number",
|
|
&tlc59116->io[i++].b_io);
|
|
if (ret < 0) {
|
|
dev_err(tlc59116->dev,
|
|
"Failure reading max brightness ret = %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int meson_tlc59116_i2c_probe(struct i2c_client *i2c)
|
|
{
|
|
struct device_node *np = i2c->dev.of_node;
|
|
struct meson_tlc59116 *tlc59116;
|
|
int ret;
|
|
|
|
pr_debug("%s enter,Line:%d version:%s\n",
|
|
__func__, __LINE__, MESON_TLC59116_VERSION);
|
|
|
|
if (!i2c_check_functionality(i2c->adapter,
|
|
I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL)) {
|
|
dev_err(&i2c->dev, "check_functionality failed!\n");
|
|
return -EIO;
|
|
}
|
|
|
|
tlc59116 = devm_kzalloc(&i2c->dev, sizeof(struct meson_tlc59116),
|
|
GFP_KERNEL);
|
|
if (!tlc59116)
|
|
return -ENOMEM;
|
|
|
|
tlc59116->dev = &i2c->dev;
|
|
tlc59116->i2c = i2c;
|
|
tlc59116->led_counts = device_get_child_node_count(&i2c->dev);
|
|
tlc59116->io = devm_kzalloc(&i2c->dev, tlc59116->led_counts *
|
|
sizeof(struct meson_tlc59116_io),
|
|
GFP_KERNEL);
|
|
if (!tlc59116->io)
|
|
return -ENOMEM;
|
|
|
|
tlc59116->colors = devm_kzalloc(&i2c->dev, tlc59116->led_counts *
|
|
sizeof(struct meson_tlc59116_colors),
|
|
GFP_KERNEL);
|
|
if (!tlc59116->colors)
|
|
return -ENOMEM;
|
|
|
|
tlc59116->colors_buf = devm_kzalloc(&i2c->dev, tlc59116->led_counts *
|
|
sizeof(unsigned int),
|
|
GFP_KERNEL);
|
|
if (!tlc59116->colors)
|
|
return -ENOMEM;
|
|
|
|
ret = meson_tlc59116_parse_led_dt(tlc59116, np);
|
|
if (ret < 0) {
|
|
dev_err(&i2c->dev, "%s error tlc59116 parse led dt fail!\n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
ret = check_tlc59116_valid(tlc59116);
|
|
if (ret < 0) {
|
|
dev_info(&i2c->dev, "there is no tlc59116. Exit probe process\n");
|
|
return 0;
|
|
}
|
|
|
|
meson_tlc59116_init_data(tlc59116);
|
|
i2c_set_clientdata(i2c, tlc59116);
|
|
dev_set_drvdata(&i2c->dev, tlc59116);
|
|
tlc59116->cdev.name = MESON_LEDS_CDEV_NAME;
|
|
tlc59116->cdev.brightness = 0;
|
|
tlc59116->cdev.max_brightness = 255;
|
|
tlc59116->cdev.brightness_set = tlc59116_set_brightness;
|
|
|
|
ret = led_classdev_register(tlc59116->dev, &tlc59116->cdev);
|
|
if (ret) {
|
|
dev_err(tlc59116->dev, "unable to register led! ret = %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = sysfs_create_group(&tlc59116->cdev.dev->kobj,
|
|
&tlc59116_attribute_group);
|
|
if (ret) {
|
|
dev_err(tlc59116->dev, "unable to create led sysfs! ret = %d\n",
|
|
ret);
|
|
led_classdev_unregister(&tlc59116->cdev);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void meson_tlc59116_i2c_remove(struct i2c_client *i2c)
|
|
{
|
|
struct meson_tlc59116 *tlc59116 = i2c_get_clientdata(i2c);
|
|
|
|
sysfs_remove_group(&tlc59116->cdev.dev->kobj,
|
|
&tlc59116_attribute_group);
|
|
led_classdev_unregister(&tlc59116->cdev);
|
|
}
|
|
|
|
static int meson_tlc59116_suspend(struct device *dev)
|
|
{
|
|
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
|
struct meson_tlc59116 *tlc59116 = i2c_get_clientdata(client);
|
|
|
|
if (!tlc59116) {
|
|
pr_debug("tlc59116 is NULL!\n");
|
|
return 0;
|
|
}
|
|
|
|
if (tlc59116->ignore_led_suspend)
|
|
return 0;
|
|
|
|
meson_tlc59116_i2c_write(tlc59116, MESON_TLC59116_REG_LEDOUT0, 0x0);
|
|
meson_tlc59116_i2c_write(tlc59116, MESON_TLC59116_REG_LEDOUT1, 0x0);
|
|
meson_tlc59116_i2c_write(tlc59116, MESON_TLC59116_REG_LEDOUT2, 0x0);
|
|
meson_tlc59116_i2c_write(tlc59116, MESON_TLC59116_REG_LEDOUT3, 0x0);
|
|
meson_tlc59116_i2c_write(tlc59116, MESON_TLC59116_REG_MODE_1, 0x10);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int meson_tlc59116_resume(struct device *dev)
|
|
{
|
|
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
|
struct meson_tlc59116 *tlc59116 = i2c_get_clientdata(client);
|
|
|
|
if (!tlc59116) {
|
|
pr_debug("tlc59116 is NULL!\n");
|
|
return 0;
|
|
}
|
|
|
|
if (tlc59116->ignore_led_suspend)
|
|
return 0;
|
|
|
|
meson_tlc59116_i2c_write(tlc59116, MESON_TLC59116_REG_LEDOUT0, 0xaa);
|
|
meson_tlc59116_i2c_write(tlc59116, MESON_TLC59116_REG_LEDOUT1, 0xaa);
|
|
meson_tlc59116_i2c_write(tlc59116, MESON_TLC59116_REG_LEDOUT2, 0xaa);
|
|
meson_tlc59116_i2c_write(tlc59116, MESON_TLC59116_REG_LEDOUT3, 0xaa);
|
|
meson_tlc59116_i2c_write(tlc59116, MESON_TLC59116_REG_MODE_1, 0x00);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_device_id meson_tlc59116_i2c_id[] = {
|
|
{ MESON_TLC59116_I2C_NAME, 0 },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, meson_tlc59116_i2c_id);
|
|
|
|
static const struct of_device_id meson_tlc59116_dt_match[] = {
|
|
{ .compatible = "amlogic,tlc59116_led" },
|
|
{ }
|
|
};
|
|
|
|
static SIMPLE_DEV_PM_OPS(meson_tlc59116_pm, meson_tlc59116_suspend, meson_tlc59116_resume);
|
|
|
|
static struct i2c_driver meson_tlc59116_driver = {
|
|
.driver = {
|
|
.name = MESON_TLC59116_I2C_NAME,
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = of_match_ptr(meson_tlc59116_dt_match),
|
|
.pm = &meson_tlc59116_pm,
|
|
},
|
|
.probe = meson_tlc59116_i2c_probe,
|
|
.remove = meson_tlc59116_i2c_remove,
|
|
.id_table = meson_tlc59116_i2c_id,
|
|
};
|
|
|
|
int led_tlc59116_init(void)
|
|
{
|
|
return i2c_add_driver(&meson_tlc59116_driver);
|
|
}
|
|
|
|
void __exit led_tlc59116_exit(void)
|
|
{
|
|
return i2c_del_driver(&meson_tlc59116_driver);
|
|
}
|