From: Olimpiu Dejeu <olimpiu@arcticsand.com>
To: jg1.han-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-fbdev-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
Olimpiu Dejeu <olimpiu-eV7fy4qpoLhpLGFMi4vTTA@public.gmane.org>
Subject: [PATCH 1/1] BACKLIGHT: Add Driver for ARCXCNN Chip
Date: Wed, 17 Aug 2016 19:06:39 +0000 [thread overview]
Message-ID: <1471460799-16015-1-git-send-email-olimpiu@arcticsand.com> (raw)
This is the driver for the Arctic Sand Technologies Inc. ARCXCNN LED
backlight IC family.
Signed-off-by: Olimpiu Dejeu <olimpiu@arcticsand.com>
---
drivers/video/backlight/Kconfig | 7 +
drivers/video/backlight/Makefile | 1 +
drivers/video/backlight/arcxcnn_bl.c | 499 +++++++++++++++++++++++++++++++++++
include/linux/i2c/arcxcnn.h | 41 +++
4 files changed, 547 insertions(+)
create mode 100644 drivers/video/backlight/arcxcnn_bl.c
create mode 100644 include/linux/i2c/arcxcnn.h
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 5a3eb2e..19ee87d 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -444,6 +444,13 @@ config BACKLIGHT_BD6107
help
If you have a Rohm BD6107 say Y to enable the backlight driver.
+config BACKLIGHT_ARCXCNN
+ tristate "Backlight driver for the Arctic Sands ARCxCnnnn family"
+ depends on I2C
+ help
+ If you have an ARCxCnnnn family backlight say Y to enable
+ the backlight driver.
+
endif # BACKLIGHT_CLASS_DEVICE
endif # BACKLIGHT_LCD_SUPPORT
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index bb82002..9d7f3ec 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -53,3 +53,4 @@ obj-$(CONFIG_BACKLIGHT_SAHARA) += kb3886_bl.o
obj-$(CONFIG_BACKLIGHT_TOSA) += tosa_bl.o
obj-$(CONFIG_BACKLIGHT_TPS65217) += tps65217_bl.o
obj-$(CONFIG_BACKLIGHT_WM831X) += wm831x_bl.o
+obj-$(CONFIG_BACKLIGHT_ARCXCNN) += arcxcnn_bl.o
diff --git a/drivers/video/backlight/arcxcnn_bl.c b/drivers/video/backlight/arcxcnn_bl.c
new file mode 100644
index 0000000..40a6c48
--- /dev/null
+++ b/drivers/video/backlight/arcxcnn_bl.c
@@ -0,0 +1,499 @@
+/*
+ * Backlight driver for Arctic Sands ARC_X_C_0N_0N Devices
+ *
+ * Copyright 2016 Arctic Sands Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/backlight.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/pwm.h>
+#include <linux/regulator/consumer.h>
+
+#include "linux/i2c/arcxcnn.h"
+
+#define ARCXCNN_CMD (0x00) /* Command Register */
+#define ARCXCNN_CMD_STDBY (0x80) /* I2C Standby */
+#define ARCXCNN_CMD_RESET (0x40) /* Reset */
+#define ARCXCNN_CMD_BOOST (0x10) /* Boost */
+#define ARCXCNN_CMD_OVP_MASK (0x0C) /* --- Over Voltage Threshold */
+#define ARCXCNN_CMD_OVP_XXV (0x0C) /* <reserved> Over Voltage Threshold */
+#define ARCXCNN_CMD_OVP_20V (0x08) /* 20v Over Voltage Threshold */
+#define ARCXCNN_CMD_OVP_24V (0x04) /* 24v Over Voltage Threshold */
+#define ARCXCNN_CMD_OVP_31V (0x00) /* 31.4v Over Voltage Threshold */
+#define ARCXCNN_CMD_EXT_COMP (0x01) /* part (0) or full (1) external comp */
+
+#define ARCXCNN_CONFIG (0x01) /* Configuration */
+#define ARCXCNN_STATUS1 (0x02) /* Status 1 */
+#define ARCXCNN_STATUS2 (0x03) /* Status 2 */
+#define ARCXCNN_FADECTRL (0x04) /* Fading Control */
+#define ARCXCNN_ILED_CONFIG (0x05) /* ILED Configuration */
+
+#define ARCXCNN_LEDEN (0x06) /* LED Enable Register */
+#define ARCXCNN_LEDEN_ISETEXT (0x80) /* Full-scale current set externally */
+#define ARCXCNN_LEDEN_MASK (0x3F) /* LED string enables */
+#define ARCXCNN_LEDEN_LED1 (0x01)
+#define ARCXCNN_LEDEN_LED2 (0x02)
+#define ARCXCNN_LEDEN_LED3 (0x04)
+#define ARCXCNN_LEDEN_LED4 (0x08)
+#define ARCXCNN_LEDEN_LED5 (0x10)
+#define ARCXCNN_LEDEN_LED6 (0x20)
+
+#define ARCXCNN_WLED_ISET_LSB (0x07) /* LED ISET LSB (in upper nibble) */
+#define ARCXCNN_WLED_ISET_MSB (0x08) /* LED ISET MSB (8 bits) */
+
+#define DEFAULT_BL_NAME "arctic_bl"
+#define MAX_BRIGHTNESS 4095
+
+static int s_no_reset_on_remove = 0;
+module_param_named(noreset, s_no_reset_on_remove, int, 0644);
+MODULE_PARM_DESC(noreset, "No reset on module removal");
+
+static int s_ibright = 60;
+module_param_named(ibright, s_ibright, int, 0644);
+MODULE_PARM_DESC(ibright, "Initial brightness (when no plat data)");
+
+static int s_iledstr = 0x3F;
+module_param_named(iledstr, s_iledstr, int, 0644);
+MODULE_PARM_DESC(iledstr, "Initial LED String (when no plat data)");
+
+static int s_retries = 2; /* 1 = only one try */
+module_param_named(retries, s_retries, int, 0644);
+MODULE_PARM_DESC(retries, "I2C retries attempted");
+
+enum arcxcnn_brightness_ctrl_mode {
+ PWM_BASED = 1,
+ REGISTER_BASED,
+};
+
+struct arcxcnn;
+
+struct arcxcnn {
+ char chipname[64];
+ enum arcxcnn_chip_id chip_id;
+ enum arcxcnn_brightness_ctrl_mode mode;
+ struct i2c_client *client;
+ struct backlight_device *bl;
+ struct device *dev;
+ struct arcxcnn_platform_data *pdata;
+ struct pwm_device *pwm;
+ struct regulator *supply; /* regulator for VDD input */
+};
+
+static int arcxcnn_write_byte(struct arcxcnn *lp, u8 reg, u8 data)
+{
+ s32 ret = -1;
+ int att;
+
+ for (att = 0; att < s_retries; att++) {
+ ret = i2c_smbus_write_byte_data(lp->client, reg, data);
+ if (ret >= 0)
+ return 0;
+ }
+ return ret;
+}
+
+static u8 arcxcnn_read_byte(struct arcxcnn *lp, u8 reg)
+{
+ int val;
+ int att;
+
+ for (att = 0; att < s_retries; att++) {
+ val = i2c_smbus_read_byte_data(lp->client, reg);
+ if (val >= 0)
+ return (u8)val;
+ }
+ return 0;
+}
+
+static int arcxcnn_update_bit(struct arcxcnn *lp, u8 reg, u8 mask, u8 data)
+{
+ int ret, att;
+ u8 tmp;
+
+ for (att = 0, ret = -1; att < s_retries; att++) {
+ ret = i2c_smbus_read_byte_data(lp->client, reg);
+ if (ret >= 0)
+ break;
+ }
+ if (ret < 0) {
+ dev_err(lp->dev, "failed to read 0x%.2x\n", reg);
+ return ret;
+ }
+
+ tmp = (u8)ret;
+ tmp &= ~mask;
+ tmp |= data & mask;
+
+ return arcxcnn_write_byte(lp, reg, tmp);
+}
+
+static int arcxcnn_set_brightness(struct arcxcnn *lp, u32 brightness)
+{
+ int ret;
+ u8 val;
+
+ val = (brightness & 0xF) << 4;
+ ret = arcxcnn_write_byte(lp, ARCXCNN_WLED_ISET_LSB, val);
+ if (ret < 0)
+ return ret;
+ val = (brightness >> 4);
+ ret = arcxcnn_write_byte(lp, ARCXCNN_WLED_ISET_MSB, val);
+ return ret;
+}
+
+static void arcxcnn_pwm_ctrl(struct arcxcnn *lp, int br, int max_br)
+{
+ unsigned int period = lp->pdata->period_ns;
+ unsigned int duty = br * period / max_br;
+ struct pwm_device *pwm;
+
+ /* request pwm device with the consumer name */
+ if (!lp->pwm) {
+ pwm = devm_pwm_get(lp->dev, lp->chipname);
+ if (IS_ERR(pwm))
+ return;
+
+ lp->pwm = pwm;
+ }
+
+ pwm_config(lp->pwm, duty, period);
+ if (duty)
+ pwm_enable(lp->pwm);
+ else
+ pwm_disable(lp->pwm);
+}
+
+static int arcxcnn_bl_update_status(struct backlight_device *bl)
+{
+ struct arcxcnn *lp = bl_get_data(bl);
+ u32 brightness = bl->props.brightness;
+
+ if (bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
+ brightness = 0;
+
+ /* set brightness */
+ if (lp->mode = PWM_BASED)
+ arcxcnn_pwm_ctrl(lp, brightness, bl->props.max_brightness);
+ else if (lp->mode = REGISTER_BASED)
+ arcxcnn_set_brightness(lp, brightness);
+
+ /* set power-on/off/save modes */
+ if (bl->props.power = 0) {
+ /* take out of standby */
+ arcxcnn_update_bit(lp, ARCXCNN_CMD, ARCXCNN_CMD_STDBY, 0);
+ }
+ else {
+ /* 1-3 = power save, 4 = off
+ * place in low-power standby mode
+ */
+ arcxcnn_update_bit(lp, ARCXCNN_CMD, ARCXCNN_CMD_STDBY, ARCXCNN_CMD_STDBY);
+ }
+ return 0;
+}
+
+static const struct backlight_ops arcxcnn_bl_ops = {
+ .options = BL_CORE_SUSPENDRESUME,
+ .update_status = arcxcnn_bl_update_status,
+};
+
+static int arcxcnn_backlight_register(struct arcxcnn *lp)
+{
+ struct backlight_device *bl;
+ struct backlight_properties props;
+ struct arcxcnn_platform_data *pdata = lp->pdata;
+ const char *name = pdata->name ? : DEFAULT_BL_NAME;
+
+ memset(&props, 0, sizeof(props));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = MAX_BRIGHTNESS;
+
+ if (pdata->initial_brightness > props.max_brightness)
+ pdata->initial_brightness = props.max_brightness;
+
+ props.brightness = pdata->initial_brightness;
+
+ bl = devm_backlight_device_register(lp->dev, name, lp->dev, lp,
+ &arcxcnn_bl_ops, &props);
+ if (IS_ERR(bl))
+ return PTR_ERR(bl);
+
+ lp->bl = bl;
+
+ return 0;
+}
+
+static ssize_t arcxcnn_get_chip_id(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct arcxcnn *lp = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", lp->chipname);
+}
+
+static ssize_t arcxcnn_get_led_str(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct arcxcnn *lp = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%02X\n", lp->pdata->ledstr);
+}
+
+static ssize_t arcxcnn_set_led_str(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t len)
+{
+ struct arcxcnn *lp = dev_get_drvdata(dev);
+ unsigned long ledstr;
+
+ if (kstrtoul(buf, 0, &ledstr))
+ return 0;
+
+ if (ledstr != lp->pdata->ledstr) {
+ /* don't allow 0 for ledstr, use power to turn all off */
+ if (ledstr = 0)
+ return 0;
+
+ lp->pdata->ledstr = ledstr & 0x3F;
+ arcxcnn_update_bit(lp, ARCXCNN_LEDEN,
+ ARCXCNN_LEDEN_MASK, lp->pdata->ledstr);
+ }
+ return len;
+}
+
+static ssize_t arcxcnn_get_bl_ctl_mode(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct arcxcnn *lp = dev_get_drvdata(dev);
+ char *strmode = NULL;
+
+ if (lp->mode = PWM_BASED)
+ strmode = "pwm based";
+ else if (lp->mode = REGISTER_BASED)
+ strmode = "register based";
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", strmode);
+}
+
+static DEVICE_ATTR(chip_id, 0444, arcxcnn_get_chip_id, NULL);
+static DEVICE_ATTR(led_str, 0664, arcxcnn_get_led_str, arcxcnn_set_led_str);
+static DEVICE_ATTR(bl_ctl_mode, 0444, arcxcnn_get_bl_ctl_mode, NULL);
+
+static struct attribute *arcxcnn_attributes[] = {
+ &dev_attr_chip_id.attr,
+ &dev_attr_led_str.attr,
+ &dev_attr_bl_ctl_mode.attr,
+ NULL,
+};
+
+static const struct attribute_group arcxcnn_attr_group = {
+ .attrs = arcxcnn_attributes,
+};
+
+#ifdef CONFIG_OF
+static int arcxcnn_parse_dt(struct arcxcnn *lp)
+{
+ struct device *dev = lp->dev;
+ struct device_node *node = dev->of_node;
+ u32 prog_length;
+
+ if (!node) {
+ dev_err(dev, "no platform data\n");
+ return -EINVAL;
+ }
+
+ of_property_read_string(node, "bl-name", &lp->pdata->name);
+ of_property_read_u16(node, "init-brt", &lp->pdata->initial_brightness);
+ of_property_read_u32(node, "pwm-period", &lp->pdata->period_ns);
+
+ if (lp->pdata->initial_brightness > MAX_BRIGHTNESS)
+ lp->pdata->initial_brightness = MAX_BRIGHTNESS;
+
+ /* Fill program from platform data if defined
+ */
+ lp->pdata->prog_entries = 0;
+ prog_length = of_get_child_count(node);
+ if (prog_length > 0) {
+ struct device_node *child;
+ u32 i = 0;
+
+ for_each_child_of_node(node, child) {
+ if (i >= ARCXCNN_MAX_PROGENTRIES)
+ break;
+ of_property_read_u8(child, "prg-addr", &lp->pdata->prog_addr[i]);
+ of_property_read_u8(child, "prg-data", &lp->pdata->prog_data[i]);
+ i++;
+ }
+ lp->pdata->prog_entries = i;
+ }
+ return 0;
+}
+#else
+static int arcxcnn_parse_dt(struct arcxcnn *lp)
+{
+ return -EINVAL;
+}
+#endif
+
+static int arcxcnn_probe(struct i2c_client *cl, const struct i2c_device_id *id)
+{
+ struct arcxcnn *lp;
+ int ret;
+ u32 i;
+ u8 regval;
+ u16 chipid;
+
+ if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+ return -EIO;
+
+ lp = devm_kzalloc(&cl->dev, sizeof(struct arcxcnn), GFP_KERNEL);
+ if (!lp)
+ return -ENOMEM;
+
+ lp->client = cl;
+ lp->dev = &cl->dev;
+ lp->chip_id = id->driver_data;
+ lp->pdata = dev_get_platdata(&cl->dev);
+
+ if (!lp->pdata) {
+ lp->pdata = devm_kzalloc(lp->dev, sizeof(*lp->pdata), GFP_KERNEL);
+ if (!lp->pdata)
+ return -ENOMEM;
+
+ /* no platform data, parse the device-tree for info. if there
+ * is no device tree entry, we are being told we exist because
+ * user-land said so, so make up the info we need
+ */
+ ret = arcxcnn_parse_dt(lp);
+ if (ret < 0) {
+ /* no device tree, use defaults based on parms
+ */
+ lp->pdata->name = NULL;
+ lp->pdata->initial_brightness = s_ibright;
+ lp->pdata->ledstr = s_iledstr;
+ lp->pdata->period_ns = 0;
+ lp->pdata->prog_entries = 0;
+
+ /* example of prog use: uncomment to test
+ lp->pdata->prog_addr[0] = 0x06;
+ lp->pdata->prog_data[0] = 0x15;
+ lp->pdata->prog_entries = 1;
+ */
+ }
+ }
+
+ if (lp->pdata->period_ns > 0)
+ lp->mode = PWM_BASED;
+ else
+ lp->mode = REGISTER_BASED;
+
+ i2c_set_clientdata(cl, lp);
+
+ /* read device ID */
+ regval = arcxcnn_read_byte(lp, 0x1E);
+ chipid = regval;
+ chipid <<= 8;
+ regval = arcxcnn_read_byte(lp, 0x1F);
+ chipid |= regval;
+
+ /* make sure it belongs to this driver
+ * TODO - handle specific ids
+ */
+ if (chipid != 0x02A5) {
+ #if 1
+ dev_info(&cl->dev, "Chip Id is %04X\n", chipid);
+ #else
+ dev_err(&cl->dev, "%04X is not ARC2C\n", chipid);
+ return -ENODEV;
+ #endif
+ }
+ /* reset the device */
+ arcxcnn_write_byte(lp, ARCXCNN_CMD, ARCXCNN_CMD_RESET);
+
+ /* set initial brightness */
+ arcxcnn_set_brightness(lp, lp->pdata->initial_brightness);
+
+ /* enable internal register mode for brightness */
+ arcxcnn_write_byte(lp, ARCXCNN_ILED_CONFIG, 0x57);
+
+ /* set initial LED Strings */
+ arcxcnn_update_bit(lp, ARCXCNN_LEDEN,
+ ARCXCNN_LEDEN_MASK, lp->pdata->ledstr);
+
+ snprintf(lp->chipname, sizeof(lp->chipname), "%s-%04X", id->name, chipid);
+
+ ret = arcxcnn_backlight_register(lp);
+ if (ret) {
+ dev_err(lp->dev,
+ "failed to register backlight. err: %d\n", ret);
+ return ret;
+ }
+
+ ret = sysfs_create_group(&lp->dev->kobj, &arcxcnn_attr_group);
+ if (ret) {
+ dev_err(lp->dev, "failed to register sysfs. err: %d\n", ret);
+ return ret;
+ }
+
+ /* playback program now that its all installed */
+ for (i = 0; i < lp->pdata->prog_entries; i++) {
+ arcxcnn_write_byte(lp, lp->pdata->prog_addr[i], lp->pdata->prog_data[i]);
+ /*
+ printk(KERN_ALERT "%02X, %02X\n",
+ lp->pdata->prog_addr[i], lp->pdata->prog_data[i]);
+ */
+ }
+
+ backlight_update_status(lp->bl);
+ return 0;
+}
+
+static int arcxcnn_remove(struct i2c_client *cl)
+{
+ struct arcxcnn *lp = i2c_get_clientdata(cl);
+
+ if (!s_no_reset_on_remove) {
+ /* disable all strings */
+ arcxcnn_write_byte(lp, ARCXCNN_LEDEN, 0x00);
+ /* reset the device */
+ arcxcnn_write_byte(lp, ARCXCNN_CMD, ARCXCNN_CMD_RESET);
+ }
+ lp->bl->props.brightness = 0;
+ backlight_update_status(lp->bl);
+ if (lp->supply)
+ regulator_disable(lp->supply);
+ sysfs_remove_group(&lp->dev->kobj, &arcxcnn_attr_group);
+
+ return 0;
+}
+
+static const struct of_device_id arcxcnn_dt_ids[] = {
+ { .compatible = "arc,arcxcnn", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, arcxcnn_dt_ids);
+
+static const struct i2c_device_id arcxcnn_ids[] = {
+ {"arcxcnn", ARCXCNN},
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, arcxcnn_ids);
+
+static struct i2c_driver arcxcnn_driver = {
+ .driver = {
+ .name = "arcxcnn",
+ .of_match_table = of_match_ptr(arcxcnn_dt_ids),
+ },
+ .probe = arcxcnn_probe,
+ .remove = arcxcnn_remove,
+ .id_table = arcxcnn_ids,
+};
+
+module_i2c_driver(arcxcnn_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Brian Dodge <bdodge09@outlook.com>");
+MODULE_DESCRIPTION("ARCXCNN Backlight driver");
diff --git a/include/linux/i2c/arcxcnn.h b/include/linux/i2c/arcxcnn.h
new file mode 100644
index 0000000..67e8214
--- /dev/null
+++ b/include/linux/i2c/arcxcnn.h
@@ -0,0 +1,41 @@
+/*
+ * Backlight driver for Arctic Sands ARC2C0608 Backlight Devices
+ *
+ * Copyright 2016 Arctic Sands Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#ifndef _ARCXCNN_H
+#define _ARCXCNN_H
+
+enum arcxcnn_chip_id {
+ ARCXCNN,
+};
+
+enum arcxcnn_brighntess_source {
+ ARCXCNN_PWM_ONLY,
+ ARCXCNN_I2C_ONLY = 2,
+};
+
+#define ARCXCNN_MAX_PROGENTRIES 48 /* max a/v pairs for custom */
+
+/**
+ * struct arcxcnn_platform_data
+ * @name : Backlight driver name. If it is not defined, default name is set.
+ * @initial_brightness : initial value of backlight brightness
+ * @period_ns : platform specific pwm period value. unit is nano.
+ Only valid when mode is PWM_BASED.
+ * @led_str : initial LED string
+ */
+struct arcxcnn_platform_data {
+ const char *name;
+ u16 initial_brightness;
+ unsigned int period_ns;
+ u8 ledstr;
+ u8 prog_addr[ARCXCNN_MAX_PROGENTRIES];
+ u8 prog_data[ARCXCNN_MAX_PROGENTRIES];
+ u32 prog_entries;
+};
+
+#endif
WARNING: multiple messages have this Message-ID (diff)
From: Olimpiu Dejeu <olimpiu-eV7fy4qpoLhpLGFMi4vTTA@public.gmane.org>
To: jg1.han-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-fbdev-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
Olimpiu Dejeu <olimpiu-eV7fy4qpoLhpLGFMi4vTTA@public.gmane.org>
Subject: [PATCH 1/1] BACKLIGHT: Add Driver for ARCXCNN Chip
Date: Wed, 17 Aug 2016 15:06:39 -0400 [thread overview]
Message-ID: <1471460799-16015-1-git-send-email-olimpiu@arcticsand.com> (raw)
This is the driver for the Arctic Sand Technologies Inc. ARCXCNN LED
backlight IC family.
Signed-off-by: Olimpiu Dejeu <olimpiu-eV7fy4qpoLhpLGFMi4vTTA@public.gmane.org>
---
drivers/video/backlight/Kconfig | 7 +
drivers/video/backlight/Makefile | 1 +
drivers/video/backlight/arcxcnn_bl.c | 499 +++++++++++++++++++++++++++++++++++
include/linux/i2c/arcxcnn.h | 41 +++
4 files changed, 547 insertions(+)
create mode 100644 drivers/video/backlight/arcxcnn_bl.c
create mode 100644 include/linux/i2c/arcxcnn.h
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 5a3eb2e..19ee87d 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -444,6 +444,13 @@ config BACKLIGHT_BD6107
help
If you have a Rohm BD6107 say Y to enable the backlight driver.
+config BACKLIGHT_ARCXCNN
+ tristate "Backlight driver for the Arctic Sands ARCxCnnnn family"
+ depends on I2C
+ help
+ If you have an ARCxCnnnn family backlight say Y to enable
+ the backlight driver.
+
endif # BACKLIGHT_CLASS_DEVICE
endif # BACKLIGHT_LCD_SUPPORT
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index bb82002..9d7f3ec 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -53,3 +53,4 @@ obj-$(CONFIG_BACKLIGHT_SAHARA) += kb3886_bl.o
obj-$(CONFIG_BACKLIGHT_TOSA) += tosa_bl.o
obj-$(CONFIG_BACKLIGHT_TPS65217) += tps65217_bl.o
obj-$(CONFIG_BACKLIGHT_WM831X) += wm831x_bl.o
+obj-$(CONFIG_BACKLIGHT_ARCXCNN) += arcxcnn_bl.o
diff --git a/drivers/video/backlight/arcxcnn_bl.c b/drivers/video/backlight/arcxcnn_bl.c
new file mode 100644
index 0000000..40a6c48
--- /dev/null
+++ b/drivers/video/backlight/arcxcnn_bl.c
@@ -0,0 +1,499 @@
+/*
+ * Backlight driver for Arctic Sands ARC_X_C_0N_0N Devices
+ *
+ * Copyright 2016 Arctic Sands Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/backlight.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/pwm.h>
+#include <linux/regulator/consumer.h>
+
+#include "linux/i2c/arcxcnn.h"
+
+#define ARCXCNN_CMD (0x00) /* Command Register */
+#define ARCXCNN_CMD_STDBY (0x80) /* I2C Standby */
+#define ARCXCNN_CMD_RESET (0x40) /* Reset */
+#define ARCXCNN_CMD_BOOST (0x10) /* Boost */
+#define ARCXCNN_CMD_OVP_MASK (0x0C) /* --- Over Voltage Threshold */
+#define ARCXCNN_CMD_OVP_XXV (0x0C) /* <reserved> Over Voltage Threshold */
+#define ARCXCNN_CMD_OVP_20V (0x08) /* 20v Over Voltage Threshold */
+#define ARCXCNN_CMD_OVP_24V (0x04) /* 24v Over Voltage Threshold */
+#define ARCXCNN_CMD_OVP_31V (0x00) /* 31.4v Over Voltage Threshold */
+#define ARCXCNN_CMD_EXT_COMP (0x01) /* part (0) or full (1) external comp */
+
+#define ARCXCNN_CONFIG (0x01) /* Configuration */
+#define ARCXCNN_STATUS1 (0x02) /* Status 1 */
+#define ARCXCNN_STATUS2 (0x03) /* Status 2 */
+#define ARCXCNN_FADECTRL (0x04) /* Fading Control */
+#define ARCXCNN_ILED_CONFIG (0x05) /* ILED Configuration */
+
+#define ARCXCNN_LEDEN (0x06) /* LED Enable Register */
+#define ARCXCNN_LEDEN_ISETEXT (0x80) /* Full-scale current set externally */
+#define ARCXCNN_LEDEN_MASK (0x3F) /* LED string enables */
+#define ARCXCNN_LEDEN_LED1 (0x01)
+#define ARCXCNN_LEDEN_LED2 (0x02)
+#define ARCXCNN_LEDEN_LED3 (0x04)
+#define ARCXCNN_LEDEN_LED4 (0x08)
+#define ARCXCNN_LEDEN_LED5 (0x10)
+#define ARCXCNN_LEDEN_LED6 (0x20)
+
+#define ARCXCNN_WLED_ISET_LSB (0x07) /* LED ISET LSB (in upper nibble) */
+#define ARCXCNN_WLED_ISET_MSB (0x08) /* LED ISET MSB (8 bits) */
+
+#define DEFAULT_BL_NAME "arctic_bl"
+#define MAX_BRIGHTNESS 4095
+
+static int s_no_reset_on_remove = 0;
+module_param_named(noreset, s_no_reset_on_remove, int, 0644);
+MODULE_PARM_DESC(noreset, "No reset on module removal");
+
+static int s_ibright = 60;
+module_param_named(ibright, s_ibright, int, 0644);
+MODULE_PARM_DESC(ibright, "Initial brightness (when no plat data)");
+
+static int s_iledstr = 0x3F;
+module_param_named(iledstr, s_iledstr, int, 0644);
+MODULE_PARM_DESC(iledstr, "Initial LED String (when no plat data)");
+
+static int s_retries = 2; /* 1 == only one try */
+module_param_named(retries, s_retries, int, 0644);
+MODULE_PARM_DESC(retries, "I2C retries attempted");
+
+enum arcxcnn_brightness_ctrl_mode {
+ PWM_BASED = 1,
+ REGISTER_BASED,
+};
+
+struct arcxcnn;
+
+struct arcxcnn {
+ char chipname[64];
+ enum arcxcnn_chip_id chip_id;
+ enum arcxcnn_brightness_ctrl_mode mode;
+ struct i2c_client *client;
+ struct backlight_device *bl;
+ struct device *dev;
+ struct arcxcnn_platform_data *pdata;
+ struct pwm_device *pwm;
+ struct regulator *supply; /* regulator for VDD input */
+};
+
+static int arcxcnn_write_byte(struct arcxcnn *lp, u8 reg, u8 data)
+{
+ s32 ret = -1;
+ int att;
+
+ for (att = 0; att < s_retries; att++) {
+ ret = i2c_smbus_write_byte_data(lp->client, reg, data);
+ if (ret >= 0)
+ return 0;
+ }
+ return ret;
+}
+
+static u8 arcxcnn_read_byte(struct arcxcnn *lp, u8 reg)
+{
+ int val;
+ int att;
+
+ for (att = 0; att < s_retries; att++) {
+ val = i2c_smbus_read_byte_data(lp->client, reg);
+ if (val >= 0)
+ return (u8)val;
+ }
+ return 0;
+}
+
+static int arcxcnn_update_bit(struct arcxcnn *lp, u8 reg, u8 mask, u8 data)
+{
+ int ret, att;
+ u8 tmp;
+
+ for (att = 0, ret = -1; att < s_retries; att++) {
+ ret = i2c_smbus_read_byte_data(lp->client, reg);
+ if (ret >= 0)
+ break;
+ }
+ if (ret < 0) {
+ dev_err(lp->dev, "failed to read 0x%.2x\n", reg);
+ return ret;
+ }
+
+ tmp = (u8)ret;
+ tmp &= ~mask;
+ tmp |= data & mask;
+
+ return arcxcnn_write_byte(lp, reg, tmp);
+}
+
+static int arcxcnn_set_brightness(struct arcxcnn *lp, u32 brightness)
+{
+ int ret;
+ u8 val;
+
+ val = (brightness & 0xF) << 4;
+ ret = arcxcnn_write_byte(lp, ARCXCNN_WLED_ISET_LSB, val);
+ if (ret < 0)
+ return ret;
+ val = (brightness >> 4);
+ ret = arcxcnn_write_byte(lp, ARCXCNN_WLED_ISET_MSB, val);
+ return ret;
+}
+
+static void arcxcnn_pwm_ctrl(struct arcxcnn *lp, int br, int max_br)
+{
+ unsigned int period = lp->pdata->period_ns;
+ unsigned int duty = br * period / max_br;
+ struct pwm_device *pwm;
+
+ /* request pwm device with the consumer name */
+ if (!lp->pwm) {
+ pwm = devm_pwm_get(lp->dev, lp->chipname);
+ if (IS_ERR(pwm))
+ return;
+
+ lp->pwm = pwm;
+ }
+
+ pwm_config(lp->pwm, duty, period);
+ if (duty)
+ pwm_enable(lp->pwm);
+ else
+ pwm_disable(lp->pwm);
+}
+
+static int arcxcnn_bl_update_status(struct backlight_device *bl)
+{
+ struct arcxcnn *lp = bl_get_data(bl);
+ u32 brightness = bl->props.brightness;
+
+ if (bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
+ brightness = 0;
+
+ /* set brightness */
+ if (lp->mode == PWM_BASED)
+ arcxcnn_pwm_ctrl(lp, brightness, bl->props.max_brightness);
+ else if (lp->mode == REGISTER_BASED)
+ arcxcnn_set_brightness(lp, brightness);
+
+ /* set power-on/off/save modes */
+ if (bl->props.power == 0) {
+ /* take out of standby */
+ arcxcnn_update_bit(lp, ARCXCNN_CMD, ARCXCNN_CMD_STDBY, 0);
+ }
+ else {
+ /* 1-3 == power save, 4 = off
+ * place in low-power standby mode
+ */
+ arcxcnn_update_bit(lp, ARCXCNN_CMD, ARCXCNN_CMD_STDBY, ARCXCNN_CMD_STDBY);
+ }
+ return 0;
+}
+
+static const struct backlight_ops arcxcnn_bl_ops = {
+ .options = BL_CORE_SUSPENDRESUME,
+ .update_status = arcxcnn_bl_update_status,
+};
+
+static int arcxcnn_backlight_register(struct arcxcnn *lp)
+{
+ struct backlight_device *bl;
+ struct backlight_properties props;
+ struct arcxcnn_platform_data *pdata = lp->pdata;
+ const char *name = pdata->name ? : DEFAULT_BL_NAME;
+
+ memset(&props, 0, sizeof(props));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = MAX_BRIGHTNESS;
+
+ if (pdata->initial_brightness > props.max_brightness)
+ pdata->initial_brightness = props.max_brightness;
+
+ props.brightness = pdata->initial_brightness;
+
+ bl = devm_backlight_device_register(lp->dev, name, lp->dev, lp,
+ &arcxcnn_bl_ops, &props);
+ if (IS_ERR(bl))
+ return PTR_ERR(bl);
+
+ lp->bl = bl;
+
+ return 0;
+}
+
+static ssize_t arcxcnn_get_chip_id(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct arcxcnn *lp = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", lp->chipname);
+}
+
+static ssize_t arcxcnn_get_led_str(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct arcxcnn *lp = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%02X\n", lp->pdata->ledstr);
+}
+
+static ssize_t arcxcnn_set_led_str(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t len)
+{
+ struct arcxcnn *lp = dev_get_drvdata(dev);
+ unsigned long ledstr;
+
+ if (kstrtoul(buf, 0, &ledstr))
+ return 0;
+
+ if (ledstr != lp->pdata->ledstr) {
+ /* don't allow 0 for ledstr, use power to turn all off */
+ if (ledstr == 0)
+ return 0;
+
+ lp->pdata->ledstr = ledstr & 0x3F;
+ arcxcnn_update_bit(lp, ARCXCNN_LEDEN,
+ ARCXCNN_LEDEN_MASK, lp->pdata->ledstr);
+ }
+ return len;
+}
+
+static ssize_t arcxcnn_get_bl_ctl_mode(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct arcxcnn *lp = dev_get_drvdata(dev);
+ char *strmode = NULL;
+
+ if (lp->mode == PWM_BASED)
+ strmode = "pwm based";
+ else if (lp->mode == REGISTER_BASED)
+ strmode = "register based";
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", strmode);
+}
+
+static DEVICE_ATTR(chip_id, 0444, arcxcnn_get_chip_id, NULL);
+static DEVICE_ATTR(led_str, 0664, arcxcnn_get_led_str, arcxcnn_set_led_str);
+static DEVICE_ATTR(bl_ctl_mode, 0444, arcxcnn_get_bl_ctl_mode, NULL);
+
+static struct attribute *arcxcnn_attributes[] = {
+ &dev_attr_chip_id.attr,
+ &dev_attr_led_str.attr,
+ &dev_attr_bl_ctl_mode.attr,
+ NULL,
+};
+
+static const struct attribute_group arcxcnn_attr_group = {
+ .attrs = arcxcnn_attributes,
+};
+
+#ifdef CONFIG_OF
+static int arcxcnn_parse_dt(struct arcxcnn *lp)
+{
+ struct device *dev = lp->dev;
+ struct device_node *node = dev->of_node;
+ u32 prog_length;
+
+ if (!node) {
+ dev_err(dev, "no platform data\n");
+ return -EINVAL;
+ }
+
+ of_property_read_string(node, "bl-name", &lp->pdata->name);
+ of_property_read_u16(node, "init-brt", &lp->pdata->initial_brightness);
+ of_property_read_u32(node, "pwm-period", &lp->pdata->period_ns);
+
+ if (lp->pdata->initial_brightness > MAX_BRIGHTNESS)
+ lp->pdata->initial_brightness = MAX_BRIGHTNESS;
+
+ /* Fill program from platform data if defined
+ */
+ lp->pdata->prog_entries = 0;
+ prog_length = of_get_child_count(node);
+ if (prog_length > 0) {
+ struct device_node *child;
+ u32 i = 0;
+
+ for_each_child_of_node(node, child) {
+ if (i >= ARCXCNN_MAX_PROGENTRIES)
+ break;
+ of_property_read_u8(child, "prg-addr", &lp->pdata->prog_addr[i]);
+ of_property_read_u8(child, "prg-data", &lp->pdata->prog_data[i]);
+ i++;
+ }
+ lp->pdata->prog_entries = i;
+ }
+ return 0;
+}
+#else
+static int arcxcnn_parse_dt(struct arcxcnn *lp)
+{
+ return -EINVAL;
+}
+#endif
+
+static int arcxcnn_probe(struct i2c_client *cl, const struct i2c_device_id *id)
+{
+ struct arcxcnn *lp;
+ int ret;
+ u32 i;
+ u8 regval;
+ u16 chipid;
+
+ if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+ return -EIO;
+
+ lp = devm_kzalloc(&cl->dev, sizeof(struct arcxcnn), GFP_KERNEL);
+ if (!lp)
+ return -ENOMEM;
+
+ lp->client = cl;
+ lp->dev = &cl->dev;
+ lp->chip_id = id->driver_data;
+ lp->pdata = dev_get_platdata(&cl->dev);
+
+ if (!lp->pdata) {
+ lp->pdata = devm_kzalloc(lp->dev, sizeof(*lp->pdata), GFP_KERNEL);
+ if (!lp->pdata)
+ return -ENOMEM;
+
+ /* no platform data, parse the device-tree for info. if there
+ * is no device tree entry, we are being told we exist because
+ * user-land said so, so make up the info we need
+ */
+ ret = arcxcnn_parse_dt(lp);
+ if (ret < 0) {
+ /* no device tree, use defaults based on parms
+ */
+ lp->pdata->name = NULL;
+ lp->pdata->initial_brightness = s_ibright;
+ lp->pdata->ledstr = s_iledstr;
+ lp->pdata->period_ns = 0;
+ lp->pdata->prog_entries = 0;
+
+ /* example of prog use: uncomment to test
+ lp->pdata->prog_addr[0] = 0x06;
+ lp->pdata->prog_data[0] = 0x15;
+ lp->pdata->prog_entries = 1;
+ */
+ }
+ }
+
+ if (lp->pdata->period_ns > 0)
+ lp->mode = PWM_BASED;
+ else
+ lp->mode = REGISTER_BASED;
+
+ i2c_set_clientdata(cl, lp);
+
+ /* read device ID */
+ regval = arcxcnn_read_byte(lp, 0x1E);
+ chipid = regval;
+ chipid <<= 8;
+ regval = arcxcnn_read_byte(lp, 0x1F);
+ chipid |= regval;
+
+ /* make sure it belongs to this driver
+ * TODO - handle specific ids
+ */
+ if (chipid != 0x02A5) {
+ #if 1
+ dev_info(&cl->dev, "Chip Id is %04X\n", chipid);
+ #else
+ dev_err(&cl->dev, "%04X is not ARC2C\n", chipid);
+ return -ENODEV;
+ #endif
+ }
+ /* reset the device */
+ arcxcnn_write_byte(lp, ARCXCNN_CMD, ARCXCNN_CMD_RESET);
+
+ /* set initial brightness */
+ arcxcnn_set_brightness(lp, lp->pdata->initial_brightness);
+
+ /* enable internal register mode for brightness */
+ arcxcnn_write_byte(lp, ARCXCNN_ILED_CONFIG, 0x57);
+
+ /* set initial LED Strings */
+ arcxcnn_update_bit(lp, ARCXCNN_LEDEN,
+ ARCXCNN_LEDEN_MASK, lp->pdata->ledstr);
+
+ snprintf(lp->chipname, sizeof(lp->chipname), "%s-%04X", id->name, chipid);
+
+ ret = arcxcnn_backlight_register(lp);
+ if (ret) {
+ dev_err(lp->dev,
+ "failed to register backlight. err: %d\n", ret);
+ return ret;
+ }
+
+ ret = sysfs_create_group(&lp->dev->kobj, &arcxcnn_attr_group);
+ if (ret) {
+ dev_err(lp->dev, "failed to register sysfs. err: %d\n", ret);
+ return ret;
+ }
+
+ /* playback program now that its all installed */
+ for (i = 0; i < lp->pdata->prog_entries; i++) {
+ arcxcnn_write_byte(lp, lp->pdata->prog_addr[i], lp->pdata->prog_data[i]);
+ /*
+ printk(KERN_ALERT "%02X, %02X\n",
+ lp->pdata->prog_addr[i], lp->pdata->prog_data[i]);
+ */
+ }
+
+ backlight_update_status(lp->bl);
+ return 0;
+}
+
+static int arcxcnn_remove(struct i2c_client *cl)
+{
+ struct arcxcnn *lp = i2c_get_clientdata(cl);
+
+ if (!s_no_reset_on_remove) {
+ /* disable all strings */
+ arcxcnn_write_byte(lp, ARCXCNN_LEDEN, 0x00);
+ /* reset the device */
+ arcxcnn_write_byte(lp, ARCXCNN_CMD, ARCXCNN_CMD_RESET);
+ }
+ lp->bl->props.brightness = 0;
+ backlight_update_status(lp->bl);
+ if (lp->supply)
+ regulator_disable(lp->supply);
+ sysfs_remove_group(&lp->dev->kobj, &arcxcnn_attr_group);
+
+ return 0;
+}
+
+static const struct of_device_id arcxcnn_dt_ids[] = {
+ { .compatible = "arc,arcxcnn", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, arcxcnn_dt_ids);
+
+static const struct i2c_device_id arcxcnn_ids[] = {
+ {"arcxcnn", ARCXCNN},
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, arcxcnn_ids);
+
+static struct i2c_driver arcxcnn_driver = {
+ .driver = {
+ .name = "arcxcnn",
+ .of_match_table = of_match_ptr(arcxcnn_dt_ids),
+ },
+ .probe = arcxcnn_probe,
+ .remove = arcxcnn_remove,
+ .id_table = arcxcnn_ids,
+};
+
+module_i2c_driver(arcxcnn_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Brian Dodge <bdodge09-1ViLX0X+lBJBDgjK7y7TUQ@public.gmane.org>");
+MODULE_DESCRIPTION("ARCXCNN Backlight driver");
diff --git a/include/linux/i2c/arcxcnn.h b/include/linux/i2c/arcxcnn.h
new file mode 100644
index 0000000..67e8214
--- /dev/null
+++ b/include/linux/i2c/arcxcnn.h
@@ -0,0 +1,41 @@
+/*
+ * Backlight driver for Arctic Sands ARC2C0608 Backlight Devices
+ *
+ * Copyright 2016 Arctic Sands Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#ifndef _ARCXCNN_H
+#define _ARCXCNN_H
+
+enum arcxcnn_chip_id {
+ ARCXCNN,
+};
+
+enum arcxcnn_brighntess_source {
+ ARCXCNN_PWM_ONLY,
+ ARCXCNN_I2C_ONLY = 2,
+};
+
+#define ARCXCNN_MAX_PROGENTRIES 48 /* max a/v pairs for custom */
+
+/**
+ * struct arcxcnn_platform_data
+ * @name : Backlight driver name. If it is not defined, default name is set.
+ * @initial_brightness : initial value of backlight brightness
+ * @period_ns : platform specific pwm period value. unit is nano.
+ Only valid when mode is PWM_BASED.
+ * @led_str : initial LED string
+ */
+struct arcxcnn_platform_data {
+ const char *name;
+ u16 initial_brightness;
+ unsigned int period_ns;
+ u8 ledstr;
+ u8 prog_addr[ARCXCNN_MAX_PROGENTRIES];
+ u8 prog_data[ARCXCNN_MAX_PROGENTRIES];
+ u32 prog_entries;
+};
+
+#endif
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
WARNING: multiple messages have this Message-ID (diff)
From: Olimpiu Dejeu <olimpiu@arcticsand.com>
To: jg1.han@samsung.com
Cc: linux-kernel@vger.kernel.org, linux-fbdev@vger.kernel.org,
devicetree@vger.kernel.org,
Olimpiu Dejeu <olimpiu@arcticsand.com>
Subject: [PATCH 1/1] BACKLIGHT: Add Driver for ARCXCNN Chip
Date: Wed, 17 Aug 2016 15:06:39 -0400 [thread overview]
Message-ID: <1471460799-16015-1-git-send-email-olimpiu@arcticsand.com> (raw)
This is the driver for the Arctic Sand Technologies Inc. ARCXCNN LED
backlight IC family.
Signed-off-by: Olimpiu Dejeu <olimpiu@arcticsand.com>
---
drivers/video/backlight/Kconfig | 7 +
drivers/video/backlight/Makefile | 1 +
drivers/video/backlight/arcxcnn_bl.c | 499 +++++++++++++++++++++++++++++++++++
include/linux/i2c/arcxcnn.h | 41 +++
4 files changed, 547 insertions(+)
create mode 100644 drivers/video/backlight/arcxcnn_bl.c
create mode 100644 include/linux/i2c/arcxcnn.h
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 5a3eb2e..19ee87d 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -444,6 +444,13 @@ config BACKLIGHT_BD6107
help
If you have a Rohm BD6107 say Y to enable the backlight driver.
+config BACKLIGHT_ARCXCNN
+ tristate "Backlight driver for the Arctic Sands ARCxCnnnn family"
+ depends on I2C
+ help
+ If you have an ARCxCnnnn family backlight say Y to enable
+ the backlight driver.
+
endif # BACKLIGHT_CLASS_DEVICE
endif # BACKLIGHT_LCD_SUPPORT
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index bb82002..9d7f3ec 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -53,3 +53,4 @@ obj-$(CONFIG_BACKLIGHT_SAHARA) += kb3886_bl.o
obj-$(CONFIG_BACKLIGHT_TOSA) += tosa_bl.o
obj-$(CONFIG_BACKLIGHT_TPS65217) += tps65217_bl.o
obj-$(CONFIG_BACKLIGHT_WM831X) += wm831x_bl.o
+obj-$(CONFIG_BACKLIGHT_ARCXCNN) += arcxcnn_bl.o
diff --git a/drivers/video/backlight/arcxcnn_bl.c b/drivers/video/backlight/arcxcnn_bl.c
new file mode 100644
index 0000000..40a6c48
--- /dev/null
+++ b/drivers/video/backlight/arcxcnn_bl.c
@@ -0,0 +1,499 @@
+/*
+ * Backlight driver for Arctic Sands ARC_X_C_0N_0N Devices
+ *
+ * Copyright 2016 Arctic Sands Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/backlight.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/pwm.h>
+#include <linux/regulator/consumer.h>
+
+#include "linux/i2c/arcxcnn.h"
+
+#define ARCXCNN_CMD (0x00) /* Command Register */
+#define ARCXCNN_CMD_STDBY (0x80) /* I2C Standby */
+#define ARCXCNN_CMD_RESET (0x40) /* Reset */
+#define ARCXCNN_CMD_BOOST (0x10) /* Boost */
+#define ARCXCNN_CMD_OVP_MASK (0x0C) /* --- Over Voltage Threshold */
+#define ARCXCNN_CMD_OVP_XXV (0x0C) /* <reserved> Over Voltage Threshold */
+#define ARCXCNN_CMD_OVP_20V (0x08) /* 20v Over Voltage Threshold */
+#define ARCXCNN_CMD_OVP_24V (0x04) /* 24v Over Voltage Threshold */
+#define ARCXCNN_CMD_OVP_31V (0x00) /* 31.4v Over Voltage Threshold */
+#define ARCXCNN_CMD_EXT_COMP (0x01) /* part (0) or full (1) external comp */
+
+#define ARCXCNN_CONFIG (0x01) /* Configuration */
+#define ARCXCNN_STATUS1 (0x02) /* Status 1 */
+#define ARCXCNN_STATUS2 (0x03) /* Status 2 */
+#define ARCXCNN_FADECTRL (0x04) /* Fading Control */
+#define ARCXCNN_ILED_CONFIG (0x05) /* ILED Configuration */
+
+#define ARCXCNN_LEDEN (0x06) /* LED Enable Register */
+#define ARCXCNN_LEDEN_ISETEXT (0x80) /* Full-scale current set externally */
+#define ARCXCNN_LEDEN_MASK (0x3F) /* LED string enables */
+#define ARCXCNN_LEDEN_LED1 (0x01)
+#define ARCXCNN_LEDEN_LED2 (0x02)
+#define ARCXCNN_LEDEN_LED3 (0x04)
+#define ARCXCNN_LEDEN_LED4 (0x08)
+#define ARCXCNN_LEDEN_LED5 (0x10)
+#define ARCXCNN_LEDEN_LED6 (0x20)
+
+#define ARCXCNN_WLED_ISET_LSB (0x07) /* LED ISET LSB (in upper nibble) */
+#define ARCXCNN_WLED_ISET_MSB (0x08) /* LED ISET MSB (8 bits) */
+
+#define DEFAULT_BL_NAME "arctic_bl"
+#define MAX_BRIGHTNESS 4095
+
+static int s_no_reset_on_remove = 0;
+module_param_named(noreset, s_no_reset_on_remove, int, 0644);
+MODULE_PARM_DESC(noreset, "No reset on module removal");
+
+static int s_ibright = 60;
+module_param_named(ibright, s_ibright, int, 0644);
+MODULE_PARM_DESC(ibright, "Initial brightness (when no plat data)");
+
+static int s_iledstr = 0x3F;
+module_param_named(iledstr, s_iledstr, int, 0644);
+MODULE_PARM_DESC(iledstr, "Initial LED String (when no plat data)");
+
+static int s_retries = 2; /* 1 == only one try */
+module_param_named(retries, s_retries, int, 0644);
+MODULE_PARM_DESC(retries, "I2C retries attempted");
+
+enum arcxcnn_brightness_ctrl_mode {
+ PWM_BASED = 1,
+ REGISTER_BASED,
+};
+
+struct arcxcnn;
+
+struct arcxcnn {
+ char chipname[64];
+ enum arcxcnn_chip_id chip_id;
+ enum arcxcnn_brightness_ctrl_mode mode;
+ struct i2c_client *client;
+ struct backlight_device *bl;
+ struct device *dev;
+ struct arcxcnn_platform_data *pdata;
+ struct pwm_device *pwm;
+ struct regulator *supply; /* regulator for VDD input */
+};
+
+static int arcxcnn_write_byte(struct arcxcnn *lp, u8 reg, u8 data)
+{
+ s32 ret = -1;
+ int att;
+
+ for (att = 0; att < s_retries; att++) {
+ ret = i2c_smbus_write_byte_data(lp->client, reg, data);
+ if (ret >= 0)
+ return 0;
+ }
+ return ret;
+}
+
+static u8 arcxcnn_read_byte(struct arcxcnn *lp, u8 reg)
+{
+ int val;
+ int att;
+
+ for (att = 0; att < s_retries; att++) {
+ val = i2c_smbus_read_byte_data(lp->client, reg);
+ if (val >= 0)
+ return (u8)val;
+ }
+ return 0;
+}
+
+static int arcxcnn_update_bit(struct arcxcnn *lp, u8 reg, u8 mask, u8 data)
+{
+ int ret, att;
+ u8 tmp;
+
+ for (att = 0, ret = -1; att < s_retries; att++) {
+ ret = i2c_smbus_read_byte_data(lp->client, reg);
+ if (ret >= 0)
+ break;
+ }
+ if (ret < 0) {
+ dev_err(lp->dev, "failed to read 0x%.2x\n", reg);
+ return ret;
+ }
+
+ tmp = (u8)ret;
+ tmp &= ~mask;
+ tmp |= data & mask;
+
+ return arcxcnn_write_byte(lp, reg, tmp);
+}
+
+static int arcxcnn_set_brightness(struct arcxcnn *lp, u32 brightness)
+{
+ int ret;
+ u8 val;
+
+ val = (brightness & 0xF) << 4;
+ ret = arcxcnn_write_byte(lp, ARCXCNN_WLED_ISET_LSB, val);
+ if (ret < 0)
+ return ret;
+ val = (brightness >> 4);
+ ret = arcxcnn_write_byte(lp, ARCXCNN_WLED_ISET_MSB, val);
+ return ret;
+}
+
+static void arcxcnn_pwm_ctrl(struct arcxcnn *lp, int br, int max_br)
+{
+ unsigned int period = lp->pdata->period_ns;
+ unsigned int duty = br * period / max_br;
+ struct pwm_device *pwm;
+
+ /* request pwm device with the consumer name */
+ if (!lp->pwm) {
+ pwm = devm_pwm_get(lp->dev, lp->chipname);
+ if (IS_ERR(pwm))
+ return;
+
+ lp->pwm = pwm;
+ }
+
+ pwm_config(lp->pwm, duty, period);
+ if (duty)
+ pwm_enable(lp->pwm);
+ else
+ pwm_disable(lp->pwm);
+}
+
+static int arcxcnn_bl_update_status(struct backlight_device *bl)
+{
+ struct arcxcnn *lp = bl_get_data(bl);
+ u32 brightness = bl->props.brightness;
+
+ if (bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
+ brightness = 0;
+
+ /* set brightness */
+ if (lp->mode == PWM_BASED)
+ arcxcnn_pwm_ctrl(lp, brightness, bl->props.max_brightness);
+ else if (lp->mode == REGISTER_BASED)
+ arcxcnn_set_brightness(lp, brightness);
+
+ /* set power-on/off/save modes */
+ if (bl->props.power == 0) {
+ /* take out of standby */
+ arcxcnn_update_bit(lp, ARCXCNN_CMD, ARCXCNN_CMD_STDBY, 0);
+ }
+ else {
+ /* 1-3 == power save, 4 = off
+ * place in low-power standby mode
+ */
+ arcxcnn_update_bit(lp, ARCXCNN_CMD, ARCXCNN_CMD_STDBY, ARCXCNN_CMD_STDBY);
+ }
+ return 0;
+}
+
+static const struct backlight_ops arcxcnn_bl_ops = {
+ .options = BL_CORE_SUSPENDRESUME,
+ .update_status = arcxcnn_bl_update_status,
+};
+
+static int arcxcnn_backlight_register(struct arcxcnn *lp)
+{
+ struct backlight_device *bl;
+ struct backlight_properties props;
+ struct arcxcnn_platform_data *pdata = lp->pdata;
+ const char *name = pdata->name ? : DEFAULT_BL_NAME;
+
+ memset(&props, 0, sizeof(props));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = MAX_BRIGHTNESS;
+
+ if (pdata->initial_brightness > props.max_brightness)
+ pdata->initial_brightness = props.max_brightness;
+
+ props.brightness = pdata->initial_brightness;
+
+ bl = devm_backlight_device_register(lp->dev, name, lp->dev, lp,
+ &arcxcnn_bl_ops, &props);
+ if (IS_ERR(bl))
+ return PTR_ERR(bl);
+
+ lp->bl = bl;
+
+ return 0;
+}
+
+static ssize_t arcxcnn_get_chip_id(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct arcxcnn *lp = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", lp->chipname);
+}
+
+static ssize_t arcxcnn_get_led_str(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct arcxcnn *lp = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%02X\n", lp->pdata->ledstr);
+}
+
+static ssize_t arcxcnn_set_led_str(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t len)
+{
+ struct arcxcnn *lp = dev_get_drvdata(dev);
+ unsigned long ledstr;
+
+ if (kstrtoul(buf, 0, &ledstr))
+ return 0;
+
+ if (ledstr != lp->pdata->ledstr) {
+ /* don't allow 0 for ledstr, use power to turn all off */
+ if (ledstr == 0)
+ return 0;
+
+ lp->pdata->ledstr = ledstr & 0x3F;
+ arcxcnn_update_bit(lp, ARCXCNN_LEDEN,
+ ARCXCNN_LEDEN_MASK, lp->pdata->ledstr);
+ }
+ return len;
+}
+
+static ssize_t arcxcnn_get_bl_ctl_mode(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct arcxcnn *lp = dev_get_drvdata(dev);
+ char *strmode = NULL;
+
+ if (lp->mode == PWM_BASED)
+ strmode = "pwm based";
+ else if (lp->mode == REGISTER_BASED)
+ strmode = "register based";
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", strmode);
+}
+
+static DEVICE_ATTR(chip_id, 0444, arcxcnn_get_chip_id, NULL);
+static DEVICE_ATTR(led_str, 0664, arcxcnn_get_led_str, arcxcnn_set_led_str);
+static DEVICE_ATTR(bl_ctl_mode, 0444, arcxcnn_get_bl_ctl_mode, NULL);
+
+static struct attribute *arcxcnn_attributes[] = {
+ &dev_attr_chip_id.attr,
+ &dev_attr_led_str.attr,
+ &dev_attr_bl_ctl_mode.attr,
+ NULL,
+};
+
+static const struct attribute_group arcxcnn_attr_group = {
+ .attrs = arcxcnn_attributes,
+};
+
+#ifdef CONFIG_OF
+static int arcxcnn_parse_dt(struct arcxcnn *lp)
+{
+ struct device *dev = lp->dev;
+ struct device_node *node = dev->of_node;
+ u32 prog_length;
+
+ if (!node) {
+ dev_err(dev, "no platform data\n");
+ return -EINVAL;
+ }
+
+ of_property_read_string(node, "bl-name", &lp->pdata->name);
+ of_property_read_u16(node, "init-brt", &lp->pdata->initial_brightness);
+ of_property_read_u32(node, "pwm-period", &lp->pdata->period_ns);
+
+ if (lp->pdata->initial_brightness > MAX_BRIGHTNESS)
+ lp->pdata->initial_brightness = MAX_BRIGHTNESS;
+
+ /* Fill program from platform data if defined
+ */
+ lp->pdata->prog_entries = 0;
+ prog_length = of_get_child_count(node);
+ if (prog_length > 0) {
+ struct device_node *child;
+ u32 i = 0;
+
+ for_each_child_of_node(node, child) {
+ if (i >= ARCXCNN_MAX_PROGENTRIES)
+ break;
+ of_property_read_u8(child, "prg-addr", &lp->pdata->prog_addr[i]);
+ of_property_read_u8(child, "prg-data", &lp->pdata->prog_data[i]);
+ i++;
+ }
+ lp->pdata->prog_entries = i;
+ }
+ return 0;
+}
+#else
+static int arcxcnn_parse_dt(struct arcxcnn *lp)
+{
+ return -EINVAL;
+}
+#endif
+
+static int arcxcnn_probe(struct i2c_client *cl, const struct i2c_device_id *id)
+{
+ struct arcxcnn *lp;
+ int ret;
+ u32 i;
+ u8 regval;
+ u16 chipid;
+
+ if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+ return -EIO;
+
+ lp = devm_kzalloc(&cl->dev, sizeof(struct arcxcnn), GFP_KERNEL);
+ if (!lp)
+ return -ENOMEM;
+
+ lp->client = cl;
+ lp->dev = &cl->dev;
+ lp->chip_id = id->driver_data;
+ lp->pdata = dev_get_platdata(&cl->dev);
+
+ if (!lp->pdata) {
+ lp->pdata = devm_kzalloc(lp->dev, sizeof(*lp->pdata), GFP_KERNEL);
+ if (!lp->pdata)
+ return -ENOMEM;
+
+ /* no platform data, parse the device-tree for info. if there
+ * is no device tree entry, we are being told we exist because
+ * user-land said so, so make up the info we need
+ */
+ ret = arcxcnn_parse_dt(lp);
+ if (ret < 0) {
+ /* no device tree, use defaults based on parms
+ */
+ lp->pdata->name = NULL;
+ lp->pdata->initial_brightness = s_ibright;
+ lp->pdata->ledstr = s_iledstr;
+ lp->pdata->period_ns = 0;
+ lp->pdata->prog_entries = 0;
+
+ /* example of prog use: uncomment to test
+ lp->pdata->prog_addr[0] = 0x06;
+ lp->pdata->prog_data[0] = 0x15;
+ lp->pdata->prog_entries = 1;
+ */
+ }
+ }
+
+ if (lp->pdata->period_ns > 0)
+ lp->mode = PWM_BASED;
+ else
+ lp->mode = REGISTER_BASED;
+
+ i2c_set_clientdata(cl, lp);
+
+ /* read device ID */
+ regval = arcxcnn_read_byte(lp, 0x1E);
+ chipid = regval;
+ chipid <<= 8;
+ regval = arcxcnn_read_byte(lp, 0x1F);
+ chipid |= regval;
+
+ /* make sure it belongs to this driver
+ * TODO - handle specific ids
+ */
+ if (chipid != 0x02A5) {
+ #if 1
+ dev_info(&cl->dev, "Chip Id is %04X\n", chipid);
+ #else
+ dev_err(&cl->dev, "%04X is not ARC2C\n", chipid);
+ return -ENODEV;
+ #endif
+ }
+ /* reset the device */
+ arcxcnn_write_byte(lp, ARCXCNN_CMD, ARCXCNN_CMD_RESET);
+
+ /* set initial brightness */
+ arcxcnn_set_brightness(lp, lp->pdata->initial_brightness);
+
+ /* enable internal register mode for brightness */
+ arcxcnn_write_byte(lp, ARCXCNN_ILED_CONFIG, 0x57);
+
+ /* set initial LED Strings */
+ arcxcnn_update_bit(lp, ARCXCNN_LEDEN,
+ ARCXCNN_LEDEN_MASK, lp->pdata->ledstr);
+
+ snprintf(lp->chipname, sizeof(lp->chipname), "%s-%04X", id->name, chipid);
+
+ ret = arcxcnn_backlight_register(lp);
+ if (ret) {
+ dev_err(lp->dev,
+ "failed to register backlight. err: %d\n", ret);
+ return ret;
+ }
+
+ ret = sysfs_create_group(&lp->dev->kobj, &arcxcnn_attr_group);
+ if (ret) {
+ dev_err(lp->dev, "failed to register sysfs. err: %d\n", ret);
+ return ret;
+ }
+
+ /* playback program now that its all installed */
+ for (i = 0; i < lp->pdata->prog_entries; i++) {
+ arcxcnn_write_byte(lp, lp->pdata->prog_addr[i], lp->pdata->prog_data[i]);
+ /*
+ printk(KERN_ALERT "%02X, %02X\n",
+ lp->pdata->prog_addr[i], lp->pdata->prog_data[i]);
+ */
+ }
+
+ backlight_update_status(lp->bl);
+ return 0;
+}
+
+static int arcxcnn_remove(struct i2c_client *cl)
+{
+ struct arcxcnn *lp = i2c_get_clientdata(cl);
+
+ if (!s_no_reset_on_remove) {
+ /* disable all strings */
+ arcxcnn_write_byte(lp, ARCXCNN_LEDEN, 0x00);
+ /* reset the device */
+ arcxcnn_write_byte(lp, ARCXCNN_CMD, ARCXCNN_CMD_RESET);
+ }
+ lp->bl->props.brightness = 0;
+ backlight_update_status(lp->bl);
+ if (lp->supply)
+ regulator_disable(lp->supply);
+ sysfs_remove_group(&lp->dev->kobj, &arcxcnn_attr_group);
+
+ return 0;
+}
+
+static const struct of_device_id arcxcnn_dt_ids[] = {
+ { .compatible = "arc,arcxcnn", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, arcxcnn_dt_ids);
+
+static const struct i2c_device_id arcxcnn_ids[] = {
+ {"arcxcnn", ARCXCNN},
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, arcxcnn_ids);
+
+static struct i2c_driver arcxcnn_driver = {
+ .driver = {
+ .name = "arcxcnn",
+ .of_match_table = of_match_ptr(arcxcnn_dt_ids),
+ },
+ .probe = arcxcnn_probe,
+ .remove = arcxcnn_remove,
+ .id_table = arcxcnn_ids,
+};
+
+module_i2c_driver(arcxcnn_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Brian Dodge <bdodge09@outlook.com>");
+MODULE_DESCRIPTION("ARCXCNN Backlight driver");
diff --git a/include/linux/i2c/arcxcnn.h b/include/linux/i2c/arcxcnn.h
new file mode 100644
index 0000000..67e8214
--- /dev/null
+++ b/include/linux/i2c/arcxcnn.h
@@ -0,0 +1,41 @@
+/*
+ * Backlight driver for Arctic Sands ARC2C0608 Backlight Devices
+ *
+ * Copyright 2016 Arctic Sands Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#ifndef _ARCXCNN_H
+#define _ARCXCNN_H
+
+enum arcxcnn_chip_id {
+ ARCXCNN,
+};
+
+enum arcxcnn_brighntess_source {
+ ARCXCNN_PWM_ONLY,
+ ARCXCNN_I2C_ONLY = 2,
+};
+
+#define ARCXCNN_MAX_PROGENTRIES 48 /* max a/v pairs for custom */
+
+/**
+ * struct arcxcnn_platform_data
+ * @name : Backlight driver name. If it is not defined, default name is set.
+ * @initial_brightness : initial value of backlight brightness
+ * @period_ns : platform specific pwm period value. unit is nano.
+ Only valid when mode is PWM_BASED.
+ * @led_str : initial LED string
+ */
+struct arcxcnn_platform_data {
+ const char *name;
+ u16 initial_brightness;
+ unsigned int period_ns;
+ u8 ledstr;
+ u8 prog_addr[ARCXCNN_MAX_PROGENTRIES];
+ u8 prog_data[ARCXCNN_MAX_PROGENTRIES];
+ u32 prog_entries;
+};
+
+#endif
next reply other threads:[~2016-08-17 19:06 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-08-17 19:06 Olimpiu Dejeu [this message]
2016-08-17 19:06 ` [PATCH 1/1] BACKLIGHT: Add Driver for ARCXCNN Chip Olimpiu Dejeu
2016-08-17 19:06 ` Olimpiu Dejeu
2016-08-18 11:07 ` Mark Rutland
2016-08-18 11:07 ` Mark Rutland
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1471460799-16015-1-git-send-email-olimpiu@arcticsand.com \
--to=olimpiu@arcticsand.com \
--cc=devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
--cc=jg1.han-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org \
--cc=linux-fbdev-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
--cc=linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
--cc=olimpiu-eV7fy4qpoLhpLGFMi4vTTA@public.gmane.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.