new file mode 100644
@@ -0,0 +1,23 @@
+*O2 Micro Compact LED Strobe Light Controller
+
+Compact LED strobe light controller, can be controlled by I2C or via a
+PWM gpio controlled.
+
+Required properties:
+- compatible : "o2micro,ozl003"
+- #address-cells: must be 1
+- #size-cells: must be 0
+- reg: I2C slave address. depends on the model.
+
+Optional properties:
+- gpio-mode: if set then controlled via gpio
+
+Examples:
+
+irled: ozl003@4b {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "okay";
+ compatible = "o2micro,ozl003";
+ reg = <0x4B>;
+};
@@ -796,6 +796,8 @@ patternProperties:
description: NVIDIA
"^nxp,.*":
description: NXP Semiconductors
+ "^o2micro,.*":
+ description: O2Micro Ltd.
"^oceanic,.*":
description: Oceanic Systems (UK) Ltd.
"^oct,.*":
@@ -732,6 +732,12 @@ config LEDS_OT200
This option enables support for the LEDs on the Bachmann OT200.
Say Y to enable LEDs on the Bachmann OT200.
+config LEDS_OZL003
+ tristate "O2 Micro OZL003 Compact LED Strobe Light Controller"
+ depends on LEDS_CLASS && I2C
+ help
+ This option enables support for O2 Micro LED IC.
+
config LEDS_MENF21BMC
tristate "LED support for the MEN 14F021P00 BMC"
depends on LEDS_CLASS && MFD_MENF21BMC
@@ -73,6 +73,7 @@ obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o
obj-$(CONFIG_LEDS_NIC78BX) += leds-nic78bx.o
obj-$(CONFIG_LEDS_NS2) += leds-ns2.o
obj-$(CONFIG_LEDS_OT200) += leds-ot200.o
+obj-$(CONFIG_LEDS_OZL003) += leds-ozl003.o
obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o
obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o
obj-$(CONFIG_LEDS_PCA963X) += leds-pca963x.o
new file mode 100644
@@ -0,0 +1,306 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/leds.h>
+#include <linux/of_gpio.h>
+
+/* Register mapping */
+#define OPERATION_MODE (0x00)
+#define ISEN1_REG_SETTING (0x01)
+#define ISEN2_REG_SETTING (0x02)
+#define DURATION_WDT (0x03)
+#define STATUS_REG (0x04)
+#define PROTECTION_THRES (0x05)
+
+/* Register bit masks */
+#define OPERATION_MODE_CNTRL_MSK (0x01)
+#define OPERATION_MODE_ENA_MSK (0x02)
+#define OPERATION_MODE_VLED_MSK (0xFC)
+#define ISEN_REG_MSK (0x80)
+#define DURATION_WDT_LED_MSK (0x80)
+
+/* Default Register values */
+#define ISEN1_CURRENT (0x7D)
+#define ISEN2_CURRENT (0x7D)
+#define DEFAULT_STROBE_OP (0x00)
+#define DEFAULT_VLED_OUTPUT (0x00)
+#define DEFAULT_PROT_THRES (0x06)
+#define DEFAULT_WDT_AND_I2C_DISABLE (0x00)
+
+#define OZL003_MAX_BRIGHTNESS (127)
+
+struct ozl003 {
+ struct i2c_client *client;
+ bool gpio_mode;
+ struct led_classdev led_dev;
+ enum led_brightness brightness;
+ struct mutex lock;
+ struct work_struct work;
+};
+
+static int ozl003_i2c_read_byte(struct ozl003 *ozl003, u8 addr, u8 *val)
+{
+ int retval;
+ struct i2c_client *client = ozl003->client;
+ struct i2c_adapter *adap = client->adapter;
+ struct i2c_msg msg[2];
+
+ msg[0].addr = client->addr;
+ msg[0].len = 1;
+ msg[0].flags = 0;
+ msg[0].buf = &addr;
+
+ msg[1].addr = client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].len = 1;
+ msg[1].buf = val;
+
+ retval = i2c_transfer(adap, msg, 2);
+ if (retval < 0)
+ return retval;
+ return (retval == 2) ? 0 : -EIO;
+}
+
+static int ozl003_i2c_write_byte(struct ozl003 *ozl003, u8 addr, u8 val)
+{
+ int retval;
+ u8 buf[2];
+ struct i2c_client *client = ozl003->client;
+
+ buf[0] = addr;
+ buf[1] = val;
+
+ retval = i2c_master_send(client, buf, 2);
+ if (retval < 0)
+ return retval;
+ return (retval == 2) ? 0 : -EIO;
+}
+
+static int ozl003_set_led_operation(struct ozl003 *ozl003, bool on)
+{
+ int ret;
+ u8 val;
+
+ /* If we are using gpio to toggle LED, no need to set register */
+ if (ozl003->gpio_mode)
+ return 0;
+
+ ret = ozl003_i2c_read_byte(ozl003, DURATION_WDT, &val);
+ if (unlikely(ret)) {
+ dev_err(&(ozl003->client->dev),
+ "Failed getting WDT register ret=%d\n", ret);
+ return ret;
+ }
+ if (on)
+ val |= DURATION_WDT_LED_MSK;
+ else
+ val &= (~DURATION_WDT_LED_MSK);
+ ret = ozl003_i2c_write_byte(ozl003, DURATION_WDT, val);
+ if (unlikely(ret)) {
+ dev_err(&(ozl003->client->dev),
+ "Failed setting WDT register ret=%d\n", ret);
+ }
+ return ret;
+}
+
+static int ozl003_set_led_brightness(struct ozl003 *ozl003, enum led_brightness brt_val)
+{
+ int ret = 0;
+ u8 s1_current;
+ u8 s2_current;
+
+ s1_current = s2_current = brt_val;
+
+ ret = ozl003_i2c_write_byte(ozl003, ISEN1_REG_SETTING, s1_current);
+ if (unlikely(ret)) {
+ dev_err(&(ozl003->client->dev),
+ "Failed setting SEN1 current register ret=%d\n", ret);
+ return ret;
+ }
+ ret = ozl003_i2c_write_byte(ozl003, ISEN2_REG_SETTING, s2_current);
+ if (unlikely(ret)) {
+ dev_err(&(ozl003->client->dev),
+ "Failed setting SEN2 current ret=%d\n", ret);
+ return ret;
+ }
+ return ret;
+
+}
+
+static void ozl003_led_brightness_work(struct work_struct *work)
+{
+ struct ozl003 *ozl003 = container_of(work, struct ozl003, work);
+
+ mutex_lock(&ozl003->lock);
+
+ if (ozl003->brightness == LED_OFF) {
+ ozl003_set_led_operation(ozl003, false);
+ } else {
+ ozl003_set_led_brightness(ozl003, ozl003->brightness);
+ ozl003_set_led_operation(ozl003, true);
+ }
+
+ mutex_unlock(&ozl003->lock);
+}
+
+static void ozl003_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brt_val)
+{
+ struct ozl003 *ozl003 = container_of(led_cdev, struct ozl003, led_dev);
+ bool update_brightness = false;
+
+ mutex_lock(&ozl003->lock);
+
+ if (brt_val != ozl003->brightness)
+ update_brightness = true;
+
+ ozl003->brightness = brt_val;
+
+ mutex_unlock(&ozl003->lock);
+
+ if (update_brightness)
+ schedule_work(&ozl003->work);
+}
+
+static int ozl003_init(struct ozl003 *ozl003)
+{
+ int ret;
+ u8 mode = DEFAULT_VLED_OUTPUT;
+ u8 s1_current = 0;
+ u8 s2_current = 0;
+
+ if (ozl003->gpio_mode) {
+ mode &= (~OPERATION_MODE_CNTRL_MSK);
+ } else { /* I2C mode */
+ mode |= OPERATION_MODE_CNTRL_MSK;
+ }
+
+ ret = ozl003_i2c_write_byte(ozl003, OPERATION_MODE, mode);
+ if (unlikely(ret)) {
+ dev_err(&(ozl003->client->dev),
+ "Failed setting OP register ret=%d\n", ret);
+ return ret;
+ }
+
+ ret = ozl003_i2c_write_byte(ozl003, ISEN1_REG_SETTING, s1_current);
+ if (unlikely(ret)) {
+ dev_err(&(ozl003->client->dev),
+ "Failed setting SEN1 current register ret=%d\n", ret);
+ return ret;
+ }
+
+ ret = ozl003_i2c_write_byte(ozl003, ISEN2_REG_SETTING, s2_current);
+ if (unlikely(ret)) {
+ dev_err(&(ozl003->client->dev),
+ "Failed setting SEN2 current ret=%d\n", ret);
+ return ret;
+ }
+
+ /* disable the delay timer */
+ ret = ozl003_i2c_write_byte(ozl003, DURATION_WDT, DEFAULT_WDT_AND_I2C_DISABLE);
+ if (unlikely(ret)) {
+ dev_err(&(ozl003->client->dev),
+ "Failed setting WDT register ret=%d\n", ret);
+ }
+
+ /* enable the IC */
+ mode |= OPERATION_MODE_ENA_MSK;
+ ret = ozl003_i2c_write_byte(ozl003, OPERATION_MODE, mode);
+ if (unlikely(ret)) {
+ dev_err(&(ozl003->client->dev),
+ "Failed setting OP register ret=%d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int ozl003_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ozl003 *ozl003;
+ struct device_node *np = client->dev.of_node;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "i2c_check_functionality error\n");
+ return -EIO;
+ }
+
+ ozl003 = devm_kzalloc(&client->dev, sizeof(struct ozl003), GFP_KERNEL);
+ if (!ozl003)
+ return -ENOMEM;
+ ozl003->client = client;
+ i2c_set_clientdata(client, ozl003);
+ mutex_init(&ozl003->lock);
+ INIT_WORK(&ozl003->work, ozl003_led_brightness_work);
+
+ if (client->dev.of_node) {
+ ozl003->gpio_mode = of_property_read_bool(np, "gpio-mode");
+ dev_info(&client->dev, "gpio-mode %d\n", ozl003->gpio_mode);
+ }
+
+
+ ozl003->led_dev.max_brightness = OZL003_MAX_BRIGHTNESS;
+ ozl003->led_dev.brightness_set = ozl003_brightness_set;
+ ozl003->led_dev.name = "ozl003";
+
+ ret = ozl003_init(ozl003);
+ if (ret)
+ goto err;
+
+ ret = led_classdev_register(&client->dev, &ozl003->led_dev);
+ if (ret) {
+ dev_err(&client->dev, "led register err: %d\n", ret);
+ goto err;
+ }
+
+ return 0;
+
+err:
+ return ret;
+}
+
+static int ozl003_remove(struct i2c_client *client)
+{
+ struct ozl003 *ozl003 = i2c_get_clientdata(client);
+
+ led_classdev_unregister(&ozl003->led_dev);
+ return 0;
+}
+
+static const struct i2c_device_id ozl003_id[] = {
+ { "ozl003", 0 },
+ { }
+};
+
+static const struct of_device_id ozl003_match_table[] = {
+ {.compatible = "o2micro,ozl003",},
+ { },
+};
+
+MODULE_DEVICE_TABLE(i2c, ozl003_id);
+
+static struct i2c_driver ozl003_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "ozl003",
+ .of_match_table = ozl003_match_table,
+ },
+ .id_table = ozl003_id,
+ .probe = ozl003_probe,
+ .remove = ozl003_remove,
+};
+
+module_i2c_driver(ozl003_driver);
+
+MODULE_AUTHOR("Yue Hu <yhuamzn@amazon.com>");
+MODULE_AUTHOR("Karthik Poduval <kpoduval@lab126.com>");
+MODULE_DESCRIPTION("O2 Micro LED Controller driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ozl003-led");