misc: Add support for isl97900 led driver

Add support for isl97900 rgb led driver.

Change-Id: I497b7e341bde8648063aa7355f4e75997383e4d7
Signed-off-by: Yahui Wang <quic_yahuiw@quicinc.com>
This commit is contained in:
Yahui Wang
2022-04-18 20:59:13 +08:00
committed by Gerrit - the friendly Code Review server
parent 63e64cdf84
commit 0730de5a74
6 changed files with 465 additions and 0 deletions

View File

@@ -524,5 +524,6 @@ source "drivers/misc/cxl/Kconfig"
source "drivers/misc/ocxl/Kconfig"
source "drivers/misc/cardreader/Kconfig"
source "drivers/misc/habanalabs/Kconfig"
source "drivers/misc/isl97900_led/Kconfig"
source "drivers/misc/uacce/Kconfig"
endmenu

View File

@@ -52,6 +52,7 @@ obj-$(CONFIG_HDCP_QSEECOM) += hdcp_qseecom.o
obj-$(CONFIG_CXL_BASE) += cxl/
obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o
obj-$(CONFIG_OCXL) += ocxl/
obj-$(CONFIG_ISL97900_LED) += isl97900_led/
obj-y += cardreader/
obj-$(CONFIG_PVPANIC) += pvpanic.o
obj-$(CONFIG_HABANA_AI) += habanalabs/

View File

@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# ISL97900 LED driver configuration
#
config ISL97900_LED
tristate "ISL97900_I2C_Driver"
select REGMAP_I2C
depends on I2C
help
Support for ISL97900 LED driver IC, using this driver,
user can set different values to control the LED current
using I2C.
Say Y if you want to enable ISL97900 driver.

View File

@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_ISL97900_LED) += isl97900_led.o

View File

@@ -0,0 +1,400 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/i2c.h>
#include <linux/qti-regmap-debugfs.h>
#include <misc/isl97900_led.h>
#define ISL97900_DRIVER_NAME "isl97900-driver"
#define ISL97900_MAX_REG 0x29
#define ISL97900_ENABLE_CONTROL 0x02
#define ISL97900_LED_R_LSB 0x13
#define ISL97900_LED_G_LSB 0x14
#define ISL97900_LED_B_LSB 0x15
#define ISL97900_LED_RGB_MSB 0x17
struct isl97900_priv {
struct regmap *regmap;
struct device *dev;
u32 cali_red;
u32 cali_green;
u32 cali_blue;
};
static const struct regmap_config isl97900_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = ISL97900_MAX_REG,
};
static int isl97900_led_set_value(struct i2c_client *client,
enum isl_function event,
u32 level)
{
struct isl97900_priv *priv;
u32 level_lsb, level_msb, level_msb_cur;
u32 red_level, green_level, blue_level;
if (!client)
return -EINVAL;
priv = (struct isl97900_priv *)i2c_get_clientdata(client);
if (!priv || !priv->regmap)
return -EINVAL;
if (level > 1023)
level = 1023;
level_lsb = level & 0xFF;
level_msb = (level >> 8) & 0x03;
level_msb_cur = 0;
switch (event) {
case ISL_LED_BRIGHTNESS_RGB_LEVEL:
red_level = priv->cali_red * (level + 1) / 1024;
green_level = priv->cali_green * (level + 1) / 1024;
blue_level = priv->cali_blue * (level + 1) / 1024;
level_msb = ((red_level >> 8) & 0x03) << 6;
level_msb |= ((green_level >> 8) & 0x03) << 4;
level_msb |= ((blue_level >> 8) & 0x03) << 2;
regmap_write(priv->regmap, ISL97900_ENABLE_CONTROL, 0x04);
regmap_write(priv->regmap, ISL97900_LED_R_LSB, red_level & 0xFF);
regmap_write(priv->regmap, ISL97900_LED_G_LSB, green_level & 0xFF);
regmap_write(priv->regmap, ISL97900_LED_B_LSB, blue_level & 0xFF);
regmap_write(priv->regmap, ISL97900_LED_RGB_MSB, level_msb);
break;
case ISL_LED_BRIGHTNESS_RED_LEVEL:
regmap_read(priv->regmap, ISL97900_LED_RGB_MSB, &level_msb_cur);
level_msb = (level_msb << 6) | (level_msb_cur & 0x3C);
regmap_write(priv->regmap, ISL97900_ENABLE_CONTROL, 0x04);
regmap_write(priv->regmap, ISL97900_LED_R_LSB, level_lsb);
regmap_write(priv->regmap, ISL97900_LED_RGB_MSB, level_msb);
break;
case ISL_LED_BRIGHTNESS_GREEN_LEVEL:
regmap_read(priv->regmap, ISL97900_LED_RGB_MSB, &level_msb_cur);
level_msb = (level_msb << 4) | (level_msb_cur & 0xCC);
regmap_write(priv->regmap, ISL97900_ENABLE_CONTROL, 0x04);
regmap_write(priv->regmap, ISL97900_LED_G_LSB, level_lsb);
regmap_write(priv->regmap, ISL97900_LED_RGB_MSB, level_msb);
break;
case ISL_LED_BRIGHTNESS_BLUE_LEVEL:
regmap_read(priv->regmap, ISL97900_LED_RGB_MSB, &level_msb_cur);
level_msb = (level_msb << 2) | (level_msb_cur & 0xF0);
regmap_write(priv->regmap, ISL97900_ENABLE_CONTROL, 0x04);
regmap_write(priv->regmap, ISL97900_LED_B_LSB, level_lsb);
regmap_write(priv->regmap, ISL97900_LED_RGB_MSB, level_msb);
break;
default:
break;
}
return 0;
}
static int isl97900_led_get_value(struct i2c_client *client,
enum isl_function event,
u32 *level)
{
struct isl97900_priv *priv;
u32 level_lsb, level_msb;
if (!client || !level)
return -EINVAL;
priv = (struct isl97900_priv *)i2c_get_clientdata(client);
if (!priv || !priv->regmap)
return -EINVAL;
level_lsb = 0;
level_msb = 0;
switch (event) {
case ISL_LED_BRIGHTNESS_RED_LEVEL:
regmap_read(priv->regmap, ISL97900_LED_R_LSB, &level_lsb);
regmap_read(priv->regmap, ISL97900_LED_RGB_MSB, &level_msb);
level_msb = (level_msb >> 6) & 0x03;
*level = (level_lsb & 0xFF) + (level_msb << 8);
break;
case ISL_LED_BRIGHTNESS_GREEN_LEVEL:
regmap_read(priv->regmap, ISL97900_LED_G_LSB, &level_lsb);
regmap_read(priv->regmap, ISL97900_LED_RGB_MSB, &level_msb);
level_msb = (level_msb >> 4) & 0x03;
*level = (level_lsb & 0xFF) + (level_msb << 8);
break;
case ISL_LED_BRIGHTNESS_BLUE_LEVEL:
regmap_read(priv->regmap, ISL97900_LED_B_LSB, &level_lsb);
regmap_read(priv->regmap, ISL97900_LED_RGB_MSB, &level_msb);
level_msb = (level_msb >> 2) & 0x03;
*level = (level_lsb & 0xFF) + (level_msb << 8);
break;
case ISL_LED_BRIGHTNESS_RGB_LEVEL:
default:
*level = 0;
break;
}
return 0;
}
static ssize_t red_led_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
u32 level;
int rc;
rc = isl97900_led_get_value(client, ISL_LED_BRIGHTNESS_RED_LEVEL, &level);
if (rc)
return rc;
return scnprintf(buf, 10, "%d\n", level);
}
static ssize_t red_led_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
u32 level;
int rc;
rc = kstrtou32(buf, 10, &level);
if (rc)
return rc;
rc = isl97900_led_set_value(client, ISL_LED_BRIGHTNESS_RED_LEVEL, level);
if (rc)
return rc;
return count;
}
static DEVICE_ATTR_RW(red_led);
static ssize_t green_led_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
u32 level;
int rc;
rc = isl97900_led_get_value(client, ISL_LED_BRIGHTNESS_GREEN_LEVEL, &level);
if (rc)
return rc;
return scnprintf(buf, 10, "%d\n", level);
}
static ssize_t green_led_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
u32 level;
int rc;
rc = kstrtou32(buf, 10, &level);
if (rc)
return rc;
rc = isl97900_led_set_value(client, ISL_LED_BRIGHTNESS_GREEN_LEVEL, level);
if (rc)
return rc;
return count;
}
static DEVICE_ATTR_RW(green_led);
static ssize_t blue_led_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
u32 level;
int rc;
rc = isl97900_led_get_value(client, ISL_LED_BRIGHTNESS_BLUE_LEVEL, &level);
if (rc)
return rc;
return scnprintf(buf, 10, "%d\n", level);
}
static ssize_t blue_led_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
u32 level;
int rc;
rc = kstrtou32(buf, 10, &level);
if (rc)
return rc;
rc = isl97900_led_set_value(client, ISL_LED_BRIGHTNESS_BLUE_LEVEL, level);
if (rc)
return rc;
return count;
}
static DEVICE_ATTR_RW(blue_led);
static struct attribute *isl97900_attrs[] = {
&dev_attr_red_led.attr,
&dev_attr_green_led.attr,
&dev_attr_blue_led.attr,
NULL
};
static const struct attribute_group isl97900_attr_group = {
.attrs = isl97900_attrs,
};
static int isl97900_led_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct isl97900_priv *priv;
int rc = 0;
priv = devm_kzalloc(&client->dev, sizeof(*priv),
GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = &client->dev;
priv->regmap = devm_regmap_init_i2c(client, &isl97900_regmap_config);
if (IS_ERR_OR_NULL(priv->regmap)) {
pr_err("isl97900: failed to init regmap(%d)!\n", rc);
return -EINVAL;
}
devm_regmap_qti_debugfs_register(priv->dev, priv->regmap);
rc = sysfs_create_group(&client->dev.kobj, &isl97900_attr_group);
if (rc)
pr_err("isl97900: failed to create sysfs group(%d)!\n", rc);
i2c_set_clientdata(client, priv);
priv->cali_red = 0xFF;
priv->cali_green = 0xFF;
priv->cali_blue = 0xFF;
return 0;
}
static int isl97900_led_remove(struct i2c_client *client)
{
struct isl97900_priv *priv =
(struct isl97900_priv *)i2c_get_clientdata(client);
if (!priv)
return -EINVAL;
dev_set_drvdata(&client->dev, NULL);
sysfs_remove_group(&client->dev.kobj, &isl97900_attr_group);
return 0;
}
int isl97900_led_cali_data_update(struct device_node *node,
u32 red_level,
u32 green_level,
u32 blue_level)
{
struct i2c_client *client = of_find_i2c_device_by_node(node);
struct isl97900_priv *priv;
if (!client)
return -EINVAL;
priv = (struct isl97900_priv *)i2c_get_clientdata(client);
if (!priv)
return -EINVAL;
if (!red_level || !green_level || !blue_level)
return -EINVAL;
priv->cali_red = red_level;
priv->cali_green = green_level;
priv->cali_blue = blue_level;
return 0;
}
EXPORT_SYMBOL(isl97900_led_cali_data_update);
int isl97900_led_event(struct device_node *node,
enum isl_function event,
u32 level)
{
struct i2c_client *client = of_find_i2c_device_by_node(node);
int rc;
rc = isl97900_led_set_value(client, event, level);
return rc;
}
EXPORT_SYMBOL(isl97900_led_event);
static const struct of_device_id isl97900_of_match[] = {
{ .compatible = "qcom,isl97900-led" },
{ }
};
static struct i2c_driver isl97900_led_driver = {
.probe = isl97900_led_probe,
.remove = isl97900_led_remove,
.driver = {
.name = ISL97900_DRIVER_NAME,
.of_match_table = isl97900_of_match,
},
};
static int __init isl97900_led_init(void)
{
int rc;
rc = i2c_add_driver(&isl97900_led_driver);
if (rc)
pr_err("isl97900:: failed to add i2c driver(%d)!\n", rc);
return rc;
}
module_init(isl97900_led_init);
static void __exit isl97900_led_exit(void)
{
i2c_del_driver(&isl97900_led_driver);
}
module_exit(isl97900_led_exit);
MODULE_DESCRIPTION("ISL97900 LED driver");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef ISL97900_LED_H
#define ISL97900_LED_H
#include <linux/of.h>
enum isl_function {
ISL_LED_BRIGHTNESS_RGB_LEVEL,
ISL_LED_BRIGHTNESS_RED_LEVEL,
ISL_LED_BRIGHTNESS_GREEN_LEVEL,
ISL_LED_BRIGHTNESS_BLUE_LEVEL,
ISL_LED_BRIGHTNESS_EVENT_MAX,
};
#if IS_ENABLED(CONFIG_ISL97900_LED)
int isl97900_led_event(struct device_node *node,
enum isl_function event,
u32 level);
int isl97900_led_cali_data_update(struct device_node *node,
u32 red_level,
u32 green_level,
u32 blue_level);
#else
int isl97900_led_event(struct device_node *node,
enum isl_function event,
u32 level)
{
return 0;
}
int isl97900_led_cali_data_update(struct device_node *node,
u32 red_level,
u32 green_level,
u32 blue_level)
{
return 0;
}
#endif /* CONFIG_ISL97900_LED */
#endif