| /* |
| * This file is provided under a dual BSD/GPLv2 license. When using or |
| * redistributing this file, you may do so under either license. |
| * |
| * GPL LICENSE SUMMARY |
| * |
| * Copyright (c) 2016 BayLibre, SAS. |
| * Author: Neil Armstrong <narmstrong@baylibre.com> |
| * Copyright (C) 2014 Amlogic, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of version 2 of the GNU General Public License as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, see <http://www.gnu.org/licenses/>. |
| * The full GNU General Public License is included in this distribution |
| * in the file called COPYING. |
| * |
| * BSD LICENSE |
| * |
| * Copyright (c) 2016 BayLibre, SAS. |
| * Author: Neil Armstrong <narmstrong@baylibre.com> |
| * Copyright (C) 2014 Amlogic, Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Intel Corporation nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #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_TLC59116_VERSION "v2.0.0" |
| |
| #define MESON_TLC59116_I2C_RETRIES 5 |
| #define MESON_TLC59116_I2C_RETRY_DELAY 5 |
| #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_err("[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_err("%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_err("%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 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_info("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_data[io->b_io] = color->blue |
| = tlc59116->colors_buf[i] & 0xff; |
| color_data[io->g_io] = color->green |
| = (tlc59116->colors_buf[i] >> 8) & 0xff; |
| color_data[io->r_io] = color->red |
| = (tlc59116->colors_buf[i] >> 16) & 0xff; |
| } |
| |
| return meson_tlc59116_i2c_writes(tlc59116->i2c, |
| (MESON_TLC59116_REG_PWM_0 |
| | (0x5 << 5)), MESON_TLC59116_MAX_IO, |
| color_data); |
| } |
| |
| static ssize_t meson_tlc59116_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_info(" enter,Line:...set led colors fail!\n"); |
| return count; |
| } |
| |
| meson_tlc59116_set_colors(tlc59116); |
| mutex_unlock(&meson_tlc59116_lock); |
| return count; |
| } |
| |
| static ssize_t meson_tlc59116_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 meson_tlc59116_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 meson_tlc59116_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 meson_tlc59116_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 meson_tlc59116_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 DEVICE_ATTR(colors, 0644, meson_tlc59116_colors_show, |
| meson_tlc59116_colors_store); |
| static DEVICE_ATTR(reg, 0644, meson_tlc59116_reg_show, |
| meson_tlc59116_reg_store); |
| static DEVICE_ATTR(debug, 0644, meson_tlc59116_debug_show, |
| meson_tlc59116_debug_store); |
| static DEVICE_ATTR_RO(meson_tlc59116_io); |
| |
| static struct attribute *tlc59116_attributes[] = { |
| &dev_attr_colors.attr, |
| &dev_attr_reg.attr, |
| &dev_attr_debug.attr, |
| &dev_attr_meson_tlc59116_io.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; |
| |
| 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, |
| const struct i2c_device_id *id) |
| { |
| struct device_node *np = i2c->dev.of_node; |
| struct meson_tlc59116 *tlc59116; |
| int ret; |
| |
| pr_info("%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 = meson_tlc59116_init_data(tlc59116); |
| if (ret) { |
| dev_err(&i2c->dev, "%s error tlc59116 init led fail!\n", |
| __func__); |
| return ret; |
| } |
| |
| i2c_set_clientdata(i2c, tlc59116); |
| dev_set_drvdata(&i2c->dev, tlc59116); |
| tlc59116->cdev.name = MESON_TLC59116_I2C_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 int meson_tlc59116_i2c_remove(struct i2c_client *i2c) |
| { |
| struct meson_tlc59116 *tlc59116 = i2c_get_clientdata(i2c); |
| |
| pr_info("%s enter\n", __func__); |
| |
| sysfs_remove_group(&tlc59116->cdev.dev->kobj, |
| &tlc59116_attribute_group); |
| led_classdev_unregister(&tlc59116->cdev); |
| |
| return 0; |
| } |
| |
| static const struct i2c_device_id meson_tlc59116_i2c_id[] = { |
| { MESON_TLC59116_I2C_NAME, 0 }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(i2c, tlc59116_i2c_id); |
| |
| static const struct of_device_id meson_tlc59116_dt_match[] = { |
| { .compatible = "amlogic,tlc59116_led" }, |
| { } |
| }; |
| |
| 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), |
| }, |
| .probe = meson_tlc59116_i2c_probe, |
| .remove = meson_tlc59116_i2c_remove, |
| .id_table = meson_tlc59116_i2c_id, |
| }; |
| |
| module_i2c_driver(meson_tlc59116_driver); |
| MODULE_AUTHOR("Bichao Zheng <bichao.zheng@amlogic.com>"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("TLC59116 LED Driver"); |