You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
580 lines
13 KiB
580 lines
13 KiB
/*
|
|
* leds-pca9956b.c - NXP PCA9956B LED segment driver
|
|
*
|
|
* Copyright (C) 2017 NXP Semiconductors
|
|
*
|
|
* 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.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/string.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/leds.h>
|
|
#include <linux/err.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/slab.h>
|
|
#include "leds-pca9956b.h"
|
|
#include <linux/gpio.h>
|
|
#include <linux/delay.h>
|
|
|
|
#define PCA9956B_LED_NUM 24
|
|
#define MAX_DEVICES 32
|
|
|
|
#define DRIVER_NAME "nxp-ledseg"
|
|
#define DRIVER_VERSION "17.11.28"
|
|
#define LED_RESET_GPIO 95
|
|
|
|
struct pca9956b_chip {
|
|
struct i2c_client *client;
|
|
struct mutex lock;
|
|
struct pca9956b_led *leds;
|
|
};
|
|
|
|
struct pca9956b_led {
|
|
struct led_classdev led_cdev;
|
|
struct pca9956b_chip *chip;
|
|
int led_num;
|
|
char name[32];
|
|
};
|
|
|
|
static struct device *pca9956b_dev;
|
|
static int pca9956b_setup(struct pca9956b_chip *chip);
|
|
|
|
/*
|
|
* Read one byte from given register address.
|
|
*/
|
|
static int pca9956b_read_reg(struct pca9956b_chip *chip, int reg, uint8_t *val)
|
|
{
|
|
int ret = i2c_smbus_read_byte_data(chip->client, reg);
|
|
|
|
if (ret < 0) {
|
|
dev_err(&chip->client->dev, "failed reading register\n");
|
|
return ret;
|
|
}
|
|
|
|
*val = (uint8_t)ret;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Write one byte to the given register address.
|
|
*/
|
|
static int pca9956b_write_reg(struct pca9956b_chip *chip, int reg, uint8_t val)
|
|
{
|
|
int ret = i2c_smbus_write_byte_data(chip->client, reg, val);
|
|
|
|
if (ret < 0) {
|
|
dev_err(&chip->client->dev, "failed writing register\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Read string from device tree property and write it to the register.
|
|
*/
|
|
static int pca9956b_readWrite_reg(struct pca9956b_chip *chip,
|
|
char *readStr, int writeRegAddr)
|
|
{
|
|
struct device_node *np = chip->client->dev.of_node;
|
|
uint32_t reg_value;
|
|
int ret;
|
|
|
|
ret = of_property_read_u32(np, readStr, ®_value);
|
|
if (ret < 0) {
|
|
dev_err(&chip->client->dev,
|
|
"[%s]: Unable to read %s\n",
|
|
__func__, readStr);
|
|
return ret;
|
|
}
|
|
|
|
mutex_lock(&chip->lock);
|
|
|
|
ret = pca9956b_write_reg(chip, writeRegAddr, (uint8_t)reg_value);
|
|
if (ret < 0) {
|
|
dev_err(&chip->client->dev,
|
|
"[%s]: Unable to write %s , value = 0x%x\n",
|
|
__func__, readStr, reg_value);
|
|
mutex_unlock(&chip->lock);
|
|
return ret;
|
|
}
|
|
|
|
mutex_unlock(&chip->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Store one byte to given register address.
|
|
*/
|
|
static ssize_t pca9956b_storeReg(struct device *dev,
|
|
struct device_attribute *devattr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct pca9956b_chip *chip = dev_get_drvdata(dev);
|
|
unsigned int ret, reg_value, reg_addr;
|
|
|
|
ret = sscanf(buf, "%x %x", ®_addr, ®_value);
|
|
if (ret == 0) {
|
|
dev_err(&chip->client->dev,
|
|
"[%s] fail to pca9956b out.\n",
|
|
__func__);
|
|
return count;
|
|
}
|
|
|
|
if (reg_addr < PCA9956B_MODE1 || reg_addr > PCA9956B_IREFALL) {
|
|
dev_err(&chip->client->dev,
|
|
"[%s] Out of range. Reg = 0x%x\n",
|
|
__func__, reg_addr);
|
|
return count;
|
|
}
|
|
|
|
mutex_lock(&chip->lock);
|
|
|
|
ret = pca9956b_write_reg(chip, reg_addr, (uint8_t)reg_value);
|
|
if (ret != 0)
|
|
dev_err(&chip->client->dev,
|
|
"[%s] Operation [0x%x , %d] is failed.\n",
|
|
__func__, reg_addr, reg_value);
|
|
|
|
mutex_unlock(&chip->lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
/*
|
|
* Show all registers
|
|
*/
|
|
static ssize_t pca9956b_showReg(struct device *dev,
|
|
struct device_attribute *devattr, char *buf)
|
|
{
|
|
struct pca9956b_chip *chip = dev_get_drvdata(dev);
|
|
uint8_t reg_value = 0;
|
|
int ret, i;
|
|
char *bufp = buf;
|
|
|
|
mutex_lock(&chip->lock);
|
|
|
|
for (i = PCA9956B_MODE1; i < PCA9956B_IREFALL; i++) {
|
|
ret = pca9956b_read_reg(chip, i, ®_value);
|
|
if (ret != 0)
|
|
dev_err(&chip->client->dev,
|
|
"[%s] Reading reg[0x%x] is failed.\n",
|
|
__func__, i);
|
|
|
|
bufp += snprintf(bufp, PAGE_SIZE,
|
|
"Addr[0x%x] = 0x%x\n", i, reg_value);
|
|
}
|
|
|
|
mutex_unlock(&chip->lock);
|
|
|
|
return strlen(buf);
|
|
}
|
|
|
|
static DEVICE_ATTR(reg, 0664,
|
|
pca9956b_showReg, pca9956b_storeReg);
|
|
|
|
/*
|
|
* Show error register.
|
|
*/
|
|
static ssize_t pca9956_showErr(struct device *dev,
|
|
struct device_attribute *devattr, char *buf)
|
|
{
|
|
struct pca9956b_chip *chip = dev_get_drvdata(dev);
|
|
uint8_t reg_value = 0;
|
|
int ret, i;
|
|
char *bufp = buf;
|
|
|
|
mutex_lock(&chip->lock);
|
|
|
|
for (i = PCA9956B_EFLAG0 ; i <= PCA9956B_EFLAG4 ; i++) {
|
|
ret = pca9956b_read_reg(chip, i, ®_value);
|
|
if (ret != 0)
|
|
dev_err(&chip->client->dev,
|
|
"[%s] Reading [0x%x] is failed.\n",
|
|
__func__, i);
|
|
|
|
bufp += snprintf(bufp, PAGE_SIZE, "PCA9956B_EFLAG[%d] = 0x%x\n",
|
|
i - PCA9956B_EFLAG0, reg_value);
|
|
}
|
|
|
|
mutex_unlock(&chip->lock);
|
|
|
|
return strlen(buf);
|
|
}
|
|
static DEVICE_ATTR(err, 0664,
|
|
pca9956_showErr, NULL);
|
|
|
|
static struct attribute *attrs[] = {
|
|
&dev_attr_err.attr,
|
|
&dev_attr_reg.attr,
|
|
NULL, /* Need to NULL terminate the list of attributes */
|
|
};
|
|
|
|
static struct attribute_group attr_group = {
|
|
.attrs = attrs,
|
|
};
|
|
|
|
/*
|
|
* Individual PWM set function
|
|
*/
|
|
static void pca9956b_brightness_set(struct led_classdev *led_cdev,
|
|
enum led_brightness value)
|
|
{
|
|
struct pca9956b_led *pca9956b;
|
|
struct pca9956b_chip *chip;
|
|
int ret;
|
|
uint8_t reg_value;
|
|
|
|
pca9956b = container_of(led_cdev, struct pca9956b_led, led_cdev);
|
|
chip = pca9956b->chip;
|
|
|
|
mutex_lock(&chip->lock);
|
|
ret = pca9956b_read_reg(chip, PCA9956B_IREF0, ®_value);
|
|
mutex_unlock(&chip->lock);
|
|
if (ret != 0)
|
|
dev_err(&chip->client->dev,
|
|
"[%s] Reading PCA9956B_IREF0 reg is failed\n",
|
|
__func__);
|
|
else if (reg_value != 0x2f) {
|
|
ret = pca9956b_setup(chip);
|
|
if (ret != 0)
|
|
dev_err(&chip->client->dev,
|
|
"[%s] pca9956b_setup = %d\n", __func__, ret);
|
|
}
|
|
|
|
mutex_lock(&chip->lock);
|
|
ret = pca9956b_write_reg(chip,
|
|
PCA9956B_PWM0 + pca9956b->led_num,
|
|
value);
|
|
mutex_unlock(&chip->lock);
|
|
if (ret != 0)
|
|
dev_err(&chip->client->dev,
|
|
"[%s] pca9956b_write_reg failed = %d\n",
|
|
__func__, ret);
|
|
|
|
if (ret == -2) {
|
|
dev_err(&chip->client->dev, "[%s] is failed = %d.\n",
|
|
__func__, ret);
|
|
|
|
ret = gpio_request(LED_RESET_GPIO, "LED RESET GPIO");
|
|
if (ret < 0) {
|
|
dev_err(&chip->client->dev,
|
|
"failed opening GPIO %d\n", ret);
|
|
return;
|
|
}
|
|
|
|
gpio_export(LED_RESET_GPIO, 1);
|
|
usleep_range(200000, 400000);
|
|
ret = gpio_direction_output(LED_RESET_GPIO, 0);
|
|
if (ret < 0) {
|
|
dev_err(&chip->client->dev,
|
|
"failed setting GPIO direction %d\n", ret);
|
|
gpio_free(LED_RESET_GPIO);
|
|
return;
|
|
}
|
|
usleep_range(200000, 400000);
|
|
|
|
ret = gpio_direction_output(LED_RESET_GPIO, 1);
|
|
gpio_set_value(LED_RESET_GPIO, 1);
|
|
usleep_range(200000, 400000);
|
|
|
|
ret = pca9956b_setup(chip);
|
|
if (ret < 0)
|
|
dev_err(&chip->client->dev, "failed pca9956b_setup\n");
|
|
|
|
gpio_free(LED_RESET_GPIO);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Individual PWM get function
|
|
*/
|
|
static enum led_brightness pca9956b_brightness_get(
|
|
struct led_classdev *led_cdev)
|
|
{
|
|
struct pca9956b_led *pca9956b;
|
|
struct pca9956b_chip *chip;
|
|
int ret;
|
|
uint8_t reg_value;
|
|
|
|
pca9956b = container_of(led_cdev, struct pca9956b_led, led_cdev);
|
|
chip = pca9956b->chip;
|
|
|
|
mutex_lock(&chip->lock);
|
|
ret = pca9956b_read_reg(chip, PCA9956B_PWM0 + pca9956b->led_num,
|
|
®_value);
|
|
if (ret != 0)
|
|
dev_err(&chip->client->dev, "[%s] is failed = %d.\n",
|
|
__func__, ret);
|
|
|
|
mutex_unlock(&chip->lock);
|
|
|
|
return reg_value;
|
|
}
|
|
|
|
static int pca9956b_registerClassDevice(struct i2c_client *client,
|
|
struct pca9956b_chip *chip)
|
|
{
|
|
int i = 0, err, reg;
|
|
struct device_node *np = client->dev.of_node, *child;
|
|
struct pca9956b_led *led;
|
|
|
|
for_each_child_of_node(np, child) {
|
|
err = of_property_read_u32(child, "reg", ®);
|
|
if (err) {
|
|
of_node_put(child);
|
|
pr_err(DRIVER_NAME": Failed to get child node");
|
|
return err;
|
|
}
|
|
if (reg < 0 || reg >= PCA9956B_LED_NUM) {
|
|
of_node_put(child);
|
|
pr_err(DRIVER_NAME": Invalid reg value");
|
|
return -EINVAL;
|
|
}
|
|
|
|
led = &chip->leds[reg];
|
|
led->led_cdev.name =
|
|
of_get_property(child, "label", NULL) ? : child->name;
|
|
led->led_cdev.default_trigger =
|
|
of_get_property(child, "linux,default-trigger", NULL);
|
|
led->led_cdev.brightness_set = pca9956b_brightness_set;
|
|
led->led_cdev.brightness_get = pca9956b_brightness_get;
|
|
led->chip = chip;
|
|
led->led_num = reg;
|
|
i++;
|
|
|
|
err = led_classdev_register(&client->dev,
|
|
&led->led_cdev);
|
|
if (err < 0) {
|
|
pr_err(DRIVER_NAME": Failed to register LED class dev");
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
exit:
|
|
while (i--)
|
|
led_classdev_unregister(&chip->leds[i].led_cdev);
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Read properties and write it to register.
|
|
*/
|
|
static int pca9956b_setup(struct pca9956b_chip *chip)
|
|
{
|
|
struct device_node *np = chip->client->dev.of_node;
|
|
int ret;
|
|
uint32_t reg_value;
|
|
|
|
ret = of_property_read_u32(np, "pca9956b,support_initialize",
|
|
®_value);
|
|
if (ret < 0) {
|
|
pr_err("[%s]: Unable to pca9956b,support_initialize\n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
|
|
if (reg_value == 0)
|
|
return ret;
|
|
|
|
ret = pca9956b_readWrite_reg(chip, "pca9956b,mode1",
|
|
PCA9956B_MODE1);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = pca9956b_readWrite_reg(chip, "pca9956b,mode2",
|
|
PCA9956B_MODE2);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = pca9956b_readWrite_reg(chip, "pca9956b,ledout0",
|
|
PCA9956B_LEDOUT0);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = pca9956b_readWrite_reg(chip, "pca9956b,ledout1",
|
|
PCA9956B_LEDOUT1);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = pca9956b_readWrite_reg(chip, "pca9956b,ledout2",
|
|
PCA9956B_LEDOUT2);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = pca9956b_readWrite_reg(chip, "pca9956b,ledout3",
|
|
PCA9956B_LEDOUT3);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = pca9956b_readWrite_reg(chip, "pca9956b,ledout4",
|
|
PCA9956B_LEDOUT4);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = pca9956b_readWrite_reg(chip, "pca9956b,ledout5",
|
|
PCA9956B_LEDOUT5);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* set default IREF to all IREF */
|
|
{
|
|
int reg_addr;
|
|
|
|
ret = of_property_read_u32(np, "pca9956b,defaultiref",
|
|
®_value);
|
|
if (ret < 0) {
|
|
dev_err(&chip->client->dev,
|
|
"[%s]: Unable to read pca9956b,defaultiref\n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
mutex_lock(&chip->lock);
|
|
|
|
for (reg_addr = PCA9956B_IREF0;
|
|
reg_addr <= PCA9956B_IREF23; reg_addr++) {
|
|
ret = pca9956b_write_reg(chip, reg_addr,
|
|
(uint8_t)reg_value);
|
|
if (ret < 0) {
|
|
dev_err(&chip->client->dev,
|
|
"[%s]: Unable to write reg0x%x[0x%x]\n",
|
|
__func__, reg_addr, reg_value);
|
|
mutex_unlock(&chip->lock);
|
|
return ret;
|
|
}
|
|
}
|
|
mutex_unlock(&chip->lock);
|
|
}
|
|
|
|
/* set IREF0 ~ IREF23 if required */
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int pca9956b_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct pca9956b_chip *chip;
|
|
struct pca9956b_led *led;
|
|
int ret;
|
|
int i;
|
|
|
|
pr_info(DRIVER_NAME": (I2C) "DRIVER_VERSION"\n");
|
|
|
|
if (!i2c_check_functionality(client->adapter,
|
|
I2C_FUNC_SMBUS_BYTE_DATA)) {
|
|
dev_err(&client->dev, "SMBUS Byte Data not Supported\n");
|
|
return -EIO;
|
|
}
|
|
|
|
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
|
|
if (!chip)
|
|
return -ENOMEM;
|
|
|
|
chip->leds = devm_kzalloc(&client->dev, sizeof(*led)*PCA9956B_LED_NUM,
|
|
GFP_KERNEL);
|
|
if (!chip->leds) {
|
|
devm_kfree(&client->dev, chip);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
i2c_set_clientdata(client, chip);
|
|
|
|
mutex_init(&chip->lock);
|
|
chip->client = client;
|
|
|
|
/* LED device class registration */
|
|
ret = pca9956b_registerClassDevice(client, chip);
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
/* Configuration setup */
|
|
ret = pca9956b_setup(chip);
|
|
if (ret < 0)
|
|
goto err_setup;
|
|
|
|
pca9956b_dev = &client->dev;
|
|
|
|
ret = sysfs_create_group(&pca9956b_dev->kobj, &attr_group);
|
|
if (ret) {
|
|
dev_err(&client->dev,
|
|
"Failed to create sysfs group for pca9956b\n");
|
|
goto err_setup;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_setup:
|
|
for (i = 0; i < PCA9956B_LED_NUM; i++)
|
|
led_classdev_unregister(&chip->leds[i].led_cdev);
|
|
exit:
|
|
mutex_destroy(&chip->lock);
|
|
devm_kfree(&client->dev, chip->leds);
|
|
devm_kfree(&client->dev, chip);
|
|
return ret;
|
|
}
|
|
|
|
static int pca9956b_remove(struct i2c_client *client)
|
|
{
|
|
struct pca9956b_chip *dev = i2c_get_clientdata(client);
|
|
int i;
|
|
|
|
for (i = 0; i < PCA9956B_LED_NUM; i++)
|
|
led_classdev_unregister(&dev->leds[i].led_cdev);
|
|
|
|
sysfs_remove_group(&pca9956b_dev->kobj, &attr_group);
|
|
|
|
mutex_destroy(&dev->lock);
|
|
devm_kfree(&client->dev, dev->leds);
|
|
devm_kfree(&client->dev, dev);
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id pca9956b_dt_ids[] = {
|
|
{ .compatible = "nxp,pca9956b",},
|
|
};
|
|
|
|
static const struct i2c_device_id pca9956b_id[] = {
|
|
{DRIVER_NAME"-i2c", 0, },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, pca9956b_id);
|
|
|
|
static struct i2c_driver pca9956b_driver = {
|
|
.driver = {
|
|
.name = DRIVER_NAME"-i2c",
|
|
.of_match_table = of_match_ptr(pca9956b_dt_ids),
|
|
},
|
|
.probe = pca9956b_probe,
|
|
.remove = pca9956b_remove,
|
|
.id_table = pca9956b_id,
|
|
};
|
|
|
|
static int __init pca9956b_init(void)
|
|
{
|
|
return i2c_add_driver(&pca9956b_driver);
|
|
}
|
|
|
|
static void __exit pca9956b_exit(void)
|
|
{
|
|
i2c_del_driver(&pca9956b_driver);
|
|
}
|
|
module_init(pca9956b_init);
|
|
module_exit(pca9956b_exit);
|
|
MODULE_AUTHOR("NXP Semiconductors");
|
|
MODULE_DESCRIPTION("PCA9956B : 24-channel constant current LED driver");
|
|
MODULE_LICENSE("GPL v2");
|
|
|
|
|