[crazy hacks] N900 flashlight

From: Pavel Machek
Date: Fri Sep 15 2017 - 04:46:13 EST


Hi!

This adds /sys interface to n900 flash. So you can do stuff...

/sys/class/leds/led-controller:flash# echo 0 > ../led-controller\:indicator/brightness

Needs _way_ more work. Also... contains way too much boilerplate, see
leds-flash.c . We'll need to figure out how to share more code between
drivers.

GPL.

Best regards,

Pavel

diff --git a/arch/arm/boot/dts/omap3-n900.dts b/arch/arm/boot/dts/omap3-n900.dts
index c4e8de7..1b88494 100644
--- a/arch/arm/boot/dts/omap3-n900.dts
+++ b/arch/arm/boot/dts/omap3-n900.dts
@@ -618,16 +618,21 @@
};

adp1653: led-controller@30 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
compatible = "adi,adp1653";
reg = <0x30>;
enable-gpios = <&gpio3 24 GPIO_ACTIVE_HIGH>; /* 88 */

- flash {
+ adp_flash: flash@0 {
+ reg = <0x0>;
flash-timeout-us = <500000>;
flash-max-microamp = <320000>;
led-max-microamp = <50000>;
};
- indicator {
+ adp_indicator: indicator@1 {
+ reg = <0x1>;
led-max-microamp = <17500>;
};
};
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index b7af41e..78bbcbf 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -7,6 +7,9 @@ obj-$(CONFIG_LEDS_CLASS) += led-class.o
obj-$(CONFIG_LEDS_CLASS_FLASH) += led-class-flash.o
obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o

+obj-$(CONFIG_LEDS_CLASS_FLASH) += leds-flash.o
+obj-y += leds-adp1653.o
+
# LED Platform Drivers
obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o
obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o
diff --git a/drivers/leds/leds-adp1653.c b/drivers/leds/leds-adp1653.c
new file mode 100644
index 0000000..96444a7
--- /dev/null
+++ b/drivers/leds/leds-adp1653.c
@@ -0,0 +1,756 @@
+/*
+ * Example of i2c flash LED driver
+ *
+ * Copyright (c) 2008-2011 Nokia Corporation
+ * Copyright (c) 2011, 2017 Intel Corporation
+ * Copyright (c) 2017 Pavel Machek <pavel@xxxxxx>
+ *
+ * Based on drivers/media/i2c/, by Sakari Ailus <sakari.ailus@xxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 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.
+ */
+
+#define DEBUG
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/led-class-flash.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <media/i2c/adp1653.h>
+
+#include <media/v4l2-flash-led-class.h>
+
+/* LED numbers for Devicetree */
+#define AS_LED_FLASH 0
+#define AS_LED_INDICATOR 1
+
+enum as_mode {
+ AS_MODE_EXT_TORCH = 0,
+ AS_MODE_INDICATOR = 1,
+ AS_MODE_ASSIST = 2,
+ AS_MODE_FLASH = 3,
+ AS_MODE_OFF = 4,
+};
+
+struct fl_config {
+ u32 flash_timeout_us;
+ u32 flash_max_ua;
+ u32 assist_max_ua;
+ u32 indicator_max_ua;
+ u32 voltage_reference;
+ u32 peak;
+};
+
+struct fl_names {
+ char flash[32];
+ char indicator[32];
+};
+
+struct fl {
+ struct i2c_client *client;
+
+ struct mutex mutex;
+
+ struct led_classdev_flash fled;
+ struct led_classdev iled_cdev;
+
+ struct v4l2_flash *vf;
+ struct v4l2_flash *vfind;
+
+ struct fwnode_handle *flash_node;
+ struct fwnode_handle *indicator_node;
+
+ struct fl_config cfg;
+
+ enum as_mode mode;
+ unsigned int timeout;
+ unsigned int flash_current;
+ unsigned int assist_current;
+ unsigned int indicator_current;
+ enum v4l2_flash_strobe_source strobe_source;
+
+ int indicator_intensity_min;
+ int indicator_intensity_step;
+ int torch_intensity_min;
+ int torch_intensity_step;
+ int flash_intensity_min;
+ int flash_intensity_step;
+ int flash_timeout_min;
+ int flash_timeout_step;
+
+ int fault;
+ struct gpio_desc *enable_gpio;
+};
+
+#define fled_to_fl(__fled) container_of(__fled, struct fl, fled)
+#define iled_cdev_to_fl(__iled_cdev) \
+ container_of(__iled_cdev, struct fl, iled_cdev)
+
+/* Return negative errno else zero on success */
+static int fl_write(struct fl *flash, u8 addr, u8 val)
+{
+ struct i2c_client *client = flash->client;
+ int rval;
+
+ rval = i2c_smbus_write_byte_data(client, addr, val);
+
+ dev_dbg(&client->dev, "Write Addr:%02X Val:%02X %s\n", addr, val,
+ rval < 0 ? "fail" : "ok");
+
+ return rval;
+}
+
+/* Return negative errno else a data byte received from the device. */
+static int fl_read(struct fl *flash, u8 addr)
+{
+ struct i2c_client *client = flash->client;
+ int rval;
+
+ rval = i2c_smbus_read_byte_data(client, addr);
+
+ dev_dbg(&client->dev, "Read Addr:%02X Val:%02X %s\n", addr, rval,
+ rval < 0 ? "fail" : "ok");
+
+ return rval;
+}
+
+/* -----------------------------------------------------------------------------
+ * Hardware configuration and trigger
+ */
+
+#define TIMEOUT_MAX 820000
+#define TIMEOUT_STEP 54600
+#define TIMEOUT_MIN (TIMEOUT_MAX - ADP1653_REG_CONFIG_TMR_SET_MAX \
+ * TIMEOUT_STEP)
+#define TIMEOUT_US_TO_CODE(t) ((TIMEOUT_MAX + (TIMEOUT_STEP / 2) - (t)) \
+ / TIMEOUT_STEP)
+#define TIMEOUT_CODE_TO_US(c) (TIMEOUT_MAX - (c) * TIMEOUT_STEP)
+
+/* Write values into ADP1653 registers. */
+static int adp1653_update_hw(struct fl *flash)
+{
+ struct i2c_client *client = flash->client;
+ u8 out_sel;
+ u8 config = 0;
+ int rval;
+
+ printk("Using current %d uA, indicator %d uA, assist %d uA\n",
+ flash->flash_current, flash->indicator_current, flash->assist_current);
+ if (flash->flash_current > 100000)
+ flash->flash_current = 100000;
+ if (flash->indicator_current > ADP1653_REG_OUT_SEL_ILED_MAX)
+ flash->indicator_current = ADP1653_REG_OUT_SEL_ILED_MAX;
+ if (flash->assist_current > ADP1653_REG_OUT_SEL_HPLED_TORCH_MAX)
+ flash->assist_current = ADP1653_REG_OUT_SEL_HPLED_TORCH_MAX;
+ out_sel = flash->indicator_current << ADP1653_REG_OUT_SEL_ILED_SHIFT;
+
+ /* FIXME: use flash current somewhere? :-) */
+
+ switch (flash->mode) {
+ case AS_MODE_OFF:
+ break;
+ case AS_MODE_FLASH:
+ printk("mode: flash\n");
+#if 0
+ /* Flash mode, light on with strobe, duration from timer */
+ config = ADP1653_REG_CONFIG_TMR_CFG;
+ config |= TIMEOUT_US_TO_CODE(flash->flash_timeout->val)
+ << ADP1653_REG_CONFIG_TMR_SET_SHIFT;
+ break;
+#endif
+ case AS_MODE_EXT_TORCH:
+ printk("mode: torch\n");
+ case AS_MODE_INDICATOR:
+ printk("mode: indicator\n");
+ case AS_MODE_ASSIST:
+ printk("mode: assist\n");
+ /* Torch mode, light immediately on, duration indefinite */
+ if (flash->assist_current)
+ out_sel |= flash->assist_current << ADP1653_REG_OUT_SEL_HPLED_SHIFT;
+ break;
+ }
+
+ rval = fl_write(flash, ADP1653_REG_OUT_SEL, out_sel);
+ if (rval < 0)
+ return rval;
+
+ rval = fl_write(flash, ADP1653_REG_CONFIG, config);
+ if (rval < 0)
+ return rval;
+
+ return 0;
+}
+
+/**
+ * fl_set_config - Set flash configuration registers
+ * @flash: The flash
+ *
+ * Configure the hardware with flash, assist and indicator currents, as well as
+ * flash timeout.
+ *
+ * Return 0 on success, or a negative error code if an I2C communication error
+ * occurred.
+ */
+
+/**
+ * fl_set_control - Set flash control register
+ * @flash: The flash
+ * @mode: Desired output mode
+ * @on: Desired output state
+ *
+ * Configure the hardware with output mode and state.
+ *
+ * Return 0 on success, or a negative error code if an I2C communication error
+ * occurred.
+ */
+
+static int __fl_get_fault(struct fl *flash, u32 *_fault)
+{
+ u32 fault;
+ int rval;
+
+ fault = fl_read(flash, ADP1653_REG_FAULT);
+ if (fault < 0)
+ return fault;
+
+ flash->fault |= fault;
+
+ if (!flash->fault)
+ return 0;
+
+ /* Clear faults. */
+ rval = fl_write(flash, ADP1653_REG_OUT_SEL, 0);
+ if (rval < 0)
+ return rval;
+
+ printk("Flash: fault %lx\n", fault);
+ *_fault = fault;
+ flash->mode = AS_MODE_OFF;
+
+ rval = adp1653_update_hw(flash);
+ if (rval)
+ return rval;
+
+ return flash->fault;
+}
+
+static int fl_get_fault(struct led_classdev_flash *fled, u32 *fault)
+{
+ struct fl *flash = fled_to_fl(fled);
+
+ return __fl_get_fault(flash, fault);
+}
+
+static unsigned int __fl_current_to_reg(unsigned int min, unsigned int max,
+ unsigned int step,
+ unsigned int val)
+{
+ if (val < min)
+ val = min;
+
+ if (val > max)
+ val = max;
+
+ return (val - min) / step;
+}
+
+static unsigned int fl_current_to_reg(struct fl *flash, bool is_flash,
+ unsigned int ua)
+{
+ if (is_flash)
+ return __fl_current_to_reg(flash->torch_intensity_min,
+ flash->cfg.assist_max_ua,
+ flash->torch_intensity_step, ua);
+ else
+ return __fl_current_to_reg(flash->flash_intensity_min,
+ flash->cfg.flash_max_ua,
+ flash->flash_intensity_step, ua);
+}
+
+static int fl_set_indicator_brightness(struct led_classdev *iled_cdev,
+ enum led_brightness brightness)
+{
+ struct fl *flash = iled_cdev_to_fl(iled_cdev);
+ int rval;
+
+ flash->indicator_current = brightness;
+ flash->mode = AS_MODE_INDICATOR;
+
+ return adp1653_update_hw(flash);
+}
+
+static int fl_set_assist_brightness(struct led_classdev *fled_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled = lcdev_to_flcdev(fled_cdev);
+ struct fl *flash = fled_to_fl(fled);
+ int rval;
+
+
+ flash->assist_current = brightness;
+ flash->mode = AS_MODE_ASSIST;
+
+ return adp1653_update_hw(flash);
+}
+
+static int fl_set_flash_brightness(struct led_classdev_flash *fled,
+ u32 brightness_ua)
+{
+ struct fl *flash = fled_to_fl(fled);
+
+ printk("set_flash_brightness: %d uA, %d\n", brightness_ua, fled->brightness);
+
+ flash->flash_current = brightness_ua;
+
+ return adp1653_update_hw(flash);
+}
+
+static int fl_set_flash_timeout(struct led_classdev_flash *fled,
+ u32 timeout_us)
+{
+ struct fl *flash = fled_to_fl(fled);
+
+ flash->timeout = 0; // FIXME AS_TIMER_US_TO_CODE(timeout_us);
+ return adp1653_update_hw(flash);
+}
+
+static int fl_set_strobe(struct led_classdev_flash *fled, bool state)
+{
+ struct fl *flash = fled_to_fl(fled);
+
+ flash->mode = AS_MODE_FLASH;
+ return adp1653_update_hw(flash);
+}
+
+static const struct led_flash_ops fl_led_flash_ops = {
+ .flash_brightness_set = fl_set_flash_brightness,
+ .timeout_set = fl_set_flash_timeout,
+ .strobe_set = fl_set_strobe,
+ .fault_get = fl_get_fault,
+};
+
+static int fl_setup(struct fl *flash)
+{
+ struct device *dev = &flash->client->dev;
+ u32 fault = 0;
+ int rval;
+
+ /* clear errors */
+ rval = __fl_get_fault(flash, &fault);
+ if (rval < 0)
+ return rval;
+
+ dev_dbg(dev, "Fault info: %02x\n", rval);
+
+ flash->mode = AS_MODE_OFF;
+
+ rval = adp1653_update_hw(flash);
+ if (rval < 0)
+ return rval;
+
+ /* read status */
+ rval = fl_get_fault(&flash->fled, &fault);
+ if (rval < 0)
+ return rval;
+
+ return rval ? -EIO : 0;
+}
+
+static int
+adp1653_init_device(struct fl *flash)
+{
+ struct i2c_client *client = flash->client;
+ int rval;
+
+ /* Clear FAULT register by writing zero to OUT_SEL */
+ rval = fl_write(flash, ADP1653_REG_OUT_SEL, 0);
+ if (rval < 0) {
+ dev_err(&client->dev, "failed writing fault register\n");
+ return -EIO;
+ }
+
+ /* Reset faults before reading new ones. */
+ flash->fault = 0;
+ return 0;
+}
+
+static int fl_test(struct fl *flash)
+{
+ int i;
+ flash->flash_current = 0;
+ for (i = 0; i<100; i++) {
+ flash->assist_current = i*100;
+ flash->mode = AS_MODE_ASSIST;
+ adp1653_update_hw(flash);
+ mdelay(50);
+ }
+ flash->mode = AS_MODE_OFF;
+ adp1653_update_hw(flash);
+
+}
+
+
+static int fl_detect(struct fl *flash)
+{
+ struct device *dev = &flash->client->dev;
+ int rval, man, model, rfu, version;
+ const char *vendor;
+ u32 fault;
+
+ rval = adp1653_init_device(flash);
+ if (rval)
+ return rval;
+
+ printk("flash: testing\n");
+ fl_test(flash);
+ printk("flash: test done\n");
+
+ rval = __fl_get_fault(flash, &fault);
+ return rval;
+}
+
+static int fl_parse_node(struct fl *flash,
+ struct fl_names *names,
+ struct fwnode_handle *fwnode)
+{
+ struct fl_config *cfg = &flash->cfg;
+ struct fwnode_handle *child;
+ const char *name;
+ const char *str;
+ int rval;
+ int on = 1;
+
+ flash->enable_gpio = devm_gpiod_get(&flash->client->dev, "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(flash->enable_gpio)) {
+ dev_err(&flash->client->dev, "Error getting GPIO\n");
+ return PTR_ERR(flash->enable_gpio);
+ }
+
+ gpiod_set_value(flash->enable_gpio, on);
+ if (on)
+ /* Some delay is apparently required. */
+ udelay(20);
+
+ fwnode_for_each_child_node(fwnode, child) {
+ u32 id = 0;
+
+ fwnode_property_read_u32(
+ child, is_of_node(child) ? "reg" : "led", &id);
+
+ switch (id) {
+ case AS_LED_FLASH:
+ flash->flash_node = child;
+ break;
+ case AS_LED_INDICATOR:
+ flash->indicator_node = child;
+ break;
+ default:
+ dev_warn(&flash->client->dev,
+ "unknown LED %u encountered, ignoring\n", id);
+ break;
+ }
+ fwnode_handle_get(child);
+ }
+
+ if (!flash->flash_node) {
+ dev_err(&flash->client->dev, "can't find flash node\n");
+ return -ENODEV;
+ }
+
+ rval = fwnode_property_read_string(flash->flash_node, "label", &name);
+ if (!rval) {
+ strlcpy(names->flash, name, sizeof(names->flash));
+ } else if (is_of_node(fwnode)) {
+ snprintf(names->flash, sizeof(names->flash),
+ "%s:flash", to_of_node(fwnode)->name);
+ } else {
+ dev_err(&flash->client->dev, "flash node has no label!\n");
+ return -EINVAL;
+ }
+
+ rval = fwnode_property_read_u32(flash->flash_node, "flash-timeout-us",
+ &cfg->flash_timeout_us);
+ if (rval < 0) {
+ dev_err(&flash->client->dev,
+ "can't read flash-timeout-us property for flash\n");
+ cfg->flash_timeout_us = 1;
+ //goto out_err;
+ }
+
+ rval = fwnode_property_read_u32(flash->flash_node, "flash-max-microamp",
+ &cfg->flash_max_ua);
+ if (rval < 0) {
+ dev_err(&flash->client->dev,
+ "can't read flash-max-microamp property for flash\n");
+ cfg->flash_max_ua = 1;
+ //goto out_err;
+ }
+
+ rval = fwnode_property_read_u32(flash->flash_node, "led-max-microamp",
+ &cfg->assist_max_ua);
+ if (rval < 0) {
+ dev_err(&flash->client->dev,
+ "can't read led-max-microamp property for flash\n");
+ cfg->assist_max_ua = 1;
+ //goto out_err;
+ }
+
+ fwnode_property_read_u32(flash->flash_node, "voltage-reference",
+ &cfg->voltage_reference);
+
+ if (!flash->indicator_node) {
+ dev_warn(&flash->client->dev,
+ "can't find indicator node\n");
+ //goto out_err;
+ }
+
+ rval = fwnode_property_read_string(flash->indicator_node, "label", &name);
+ if (!rval) {
+ strlcpy(names->indicator, name, sizeof(names->indicator));
+ } else if (is_of_node(fwnode)) {
+ snprintf(names->indicator, sizeof(names->indicator),
+ "%s:indicator", to_of_node(fwnode)->name);
+ } else {
+ dev_err(&flash->client->dev, "flash node has no label!\n");
+ return -EINVAL;
+ }
+
+ rval = fwnode_property_read_u32(flash->indicator_node, "led-max-microamp",
+ &cfg->indicator_max_ua);
+ if (rval < 0) {
+ dev_err(&flash->client->dev,
+ "can't read led-max-microamp property for indicator\n");
+ goto out_err;
+ }
+
+ printk("flash: indicator_max_ua %d, assist_max_ua %d, flash_max_ua %d\n",
+ cfg->indicator_max_ua, cfg->assist_max_ua, cfg->flash_max_ua);
+
+ return 0;
+
+out_err:
+ fwnode_handle_put(flash->flash_node);
+ fwnode_handle_put(flash->indicator_node);
+
+ return rval;
+}
+
+static int fl_led_class_setup(struct fl *flash,
+ struct fl_names *names)
+{
+ struct led_classdev *fled_cdev = &flash->fled.led_cdev;
+ struct led_classdev *iled_cdev = &flash->iled_cdev;
+ struct led_flash_setting *cfg;
+ int rval;
+
+ iled_cdev->name = names->indicator;
+ iled_cdev->brightness_set_blocking = fl_set_indicator_brightness;
+ iled_cdev->max_brightness =
+ flash->cfg.indicator_max_ua / flash->indicator_intensity_step;
+ iled_cdev->flags = LED_CORE_SUSPENDRESUME;
+
+ rval = led_classdev_register(&flash->client->dev, iled_cdev);
+ if (rval < 0)
+ return rval;
+
+ cfg = &flash->fled.brightness;
+ cfg->min = flash->flash_intensity_min;
+ cfg->max = flash->cfg.flash_max_ua;
+ cfg->step = flash->flash_intensity_step;
+ cfg->val = flash->cfg.flash_max_ua;
+
+ cfg = &flash->fled.timeout;
+ cfg->min = flash->flash_timeout_min;
+ cfg->max = flash->cfg.flash_timeout_us;
+ cfg->step = flash->flash_timeout_step;
+ cfg->val = flash->cfg.flash_timeout_us;
+
+ flash->fled.ops = &fl_led_flash_ops;
+
+ fled_cdev->name = names->flash;
+ fled_cdev->brightness_set_blocking = fl_set_assist_brightness;
+ /* Value 0 is off in LED class. */
+ fled_cdev->max_brightness =
+ fl_current_to_reg(flash, false,
+ flash->cfg.assist_max_ua) + 1;
+ fled_cdev->flags = LED_DEV_CAP_FLASH | LED_CORE_SUSPENDRESUME;
+
+ rval = led_classdev_flash_register(&flash->client->dev, &flash->fled);
+ if (rval) {
+ led_classdev_unregister(iled_cdev);
+ dev_err(&flash->client->dev,
+ "led_classdev_flash_register() failed, error %d\n",
+ rval);
+ }
+
+ return rval;
+}
+
+static int fl_v4l2_setup(struct fl *flash)
+{
+ struct led_classdev_flash *fled = &flash->fled;
+ struct led_classdev *led = &fled->led_cdev;
+ struct v4l2_flash_config cfg = {
+ .intensity = {
+ .min = flash->torch_intensity_min,
+ .max = flash->cfg.assist_max_ua,
+ .step = flash->torch_intensity_step,
+ .val = flash->cfg.assist_max_ua,
+ },
+ };
+ struct v4l2_flash_config cfgind = {
+ .intensity = {
+ .min = flash->indicator_intensity_min,
+ .max = flash->cfg.indicator_max_ua,
+ .step = flash->indicator_intensity_step,
+ .val = flash->cfg.indicator_max_ua,
+ },
+ };
+
+ strlcpy(cfg.dev_name, led->name, sizeof(cfg.dev_name));
+ strlcpy(cfgind.dev_name, flash->iled_cdev.name, sizeof(cfg.dev_name));
+
+ flash->vf = v4l2_flash_init(
+ &flash->client->dev, flash->flash_node, &flash->fled, NULL,
+ &cfg);
+ if (IS_ERR(flash->vf))
+ return PTR_ERR(flash->vf);
+
+ flash->vfind = v4l2_flash_indicator_init(
+ &flash->client->dev, flash->indicator_node, &flash->iled_cdev,
+ &cfgind);
+ if (IS_ERR(flash->vfind)) {
+ v4l2_flash_release(flash->vf);
+ return PTR_ERR(flash->vfind);
+ }
+
+ return 0;
+}
+
+static int fl_probe(struct i2c_client *client)
+{
+ struct fl_names names;
+ struct fl *flash;
+ int rval;
+
+ printk("flash: probe\n");
+ if (!dev_fwnode(&client->dev))
+ return -ENODEV;
+
+ flash = devm_kzalloc(&client->dev, sizeof(*flash), GFP_KERNEL);
+ if (flash == NULL)
+ return -ENOMEM;
+
+ flash->client = client;
+
+ printk("flash: parse node\n");
+ rval = fl_parse_node(flash, &names, dev_fwnode(&client->dev));
+ if (rval < 0)
+ return rval;
+
+ printk("flash: detect\n");
+ rval = fl_detect(flash);
+ if (rval < 0)
+ goto out_put_nodes;
+
+ mutex_init(&flash->mutex);
+ i2c_set_clientdata(client, flash);
+
+ printk("flash: setup\n");
+ rval = fl_setup(flash);
+ if (rval)
+ goto out_mutex_destroy;
+
+ flash->indicator_intensity_step = ADP1653_INDICATOR_INTENSITY_STEP;
+ flash->flash_intensity_step = ADP1653_FLASH_INTENSITY_STEP;
+ flash->flash_intensity_min = ADP1653_FLASH_INTENSITY_MIN;
+ flash->torch_intensity_step = ADP1653_FLASH_INTENSITY_STEP;
+ flash->torch_intensity_min = ADP1653_TORCH_INTENSITY_MIN;
+ flash->flash_timeout_step = 1;
+
+ printk("flash: led class setup\n");
+ rval = fl_led_class_setup(flash, &names);
+ if (rval)
+ goto out_mutex_destroy;
+
+ printk("flash: v4l2 class setup\n");
+ rval = fl_v4l2_setup(flash);
+ if (rval)
+ goto out_led_classdev_flash_unregister;
+ printk("flash: all done\n");
+
+ return 0;
+
+out_led_classdev_flash_unregister:
+ led_classdev_flash_unregister(&flash->fled);
+
+out_mutex_destroy:
+ mutex_destroy(&flash->mutex);
+
+out_put_nodes:
+ fwnode_handle_put(flash->flash_node);
+ fwnode_handle_put(flash->indicator_node);
+
+ return rval;
+}
+
+static int fl_remove(struct i2c_client *client)
+{
+ struct fl *flash = i2c_get_clientdata(client);
+
+ flash->mode = AS_MODE_OFF;
+ adp1653_update_hw(flash);
+
+ v4l2_flash_release(flash->vf);
+
+ led_classdev_flash_unregister(&flash->fled);
+ led_classdev_unregister(&flash->iled_cdev);
+
+ mutex_destroy(&flash->mutex);
+
+ fwnode_handle_put(flash->flash_node);
+ fwnode_handle_put(flash->indicator_node);
+
+ return 0;
+}
+
+static const struct i2c_device_id fl_id_table[] = {
+ { ADP1653_NAME, 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, fl_id_table);
+
+static const struct of_device_id fl_of_table[] = {
+ { .compatible = "fixme?" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, fl_of_table);
+
+static struct i2c_driver fl_i2c_driver = {
+ .driver = {
+ .of_match_table = fl_of_table,
+ .name = ADP1653_NAME,
+ },
+ .probe_new = fl_probe,
+ .remove = fl_remove,
+ .id_table = fl_id_table,
+};
+
+module_i2c_driver(fl_i2c_driver);
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx>");
+MODULE_AUTHOR("Sakari Ailus <sakari.ailus@xxxxxx>");
+MODULE_AUTHOR("Pavel Machek <pavel@xxxxxx>");
+MODULE_DESCRIPTION("LED flash driver for ADP1653");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-flash.c b/drivers/leds/leds-flash.c
new file mode 100644
index 0000000..4a95a4f
--- /dev/null
+++ b/drivers/leds/leds-flash.c
@@ -0,0 +1,620 @@
+/*
+ * Example of i2c flash LED driver
+ *
+ * Copyright (C) 2008-2011 Nokia Corporation
+ * Copyright (c) 2011, 2017 Intel Corporation.
+ *
+ * Based on drivers/media/i2c/fl.c.
+ *
+ * Contact: Sakari Ailus <sakari.ailus@xxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 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.
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/led-class-flash.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+
+#include <media/v4l2-flash-led-class.h>
+
+/* LED numbers for Devicetree */
+#define AS_LED_FLASH 0
+#define AS_LED_INDICATOR 1
+
+enum as_mode {
+ AS_MODE_EXT_TORCH = 0,
+ AS_MODE_INDICATOR = 1,
+ AS_MODE_ASSIST = 2,
+ AS_MODE_FLASH = 3,
+};
+
+struct fl_config {
+ u32 flash_timeout_us;
+ u32 flash_max_ua;
+ u32 assist_max_ua;
+ u32 indicator_max_ua;
+ u32 voltage_reference;
+ u32 peak;
+};
+
+struct fl_names {
+ char flash[32];
+ char indicator[32];
+};
+
+struct fl {
+ struct i2c_client *client;
+
+ struct mutex mutex;
+
+ struct led_classdev_flash fled;
+ struct led_classdev iled_cdev;
+
+ struct v4l2_flash *vf;
+ struct v4l2_flash *vfind;
+
+ struct fwnode_handle *flash_node;
+ struct fwnode_handle *indicator_node;
+
+ struct fl_config cfg;
+
+ enum as_mode mode;
+ unsigned int timeout;
+ unsigned int flash_current;
+ unsigned int assist_current;
+ unsigned int indicator_current;
+ enum v4l2_flash_strobe_source strobe_source;
+
+ int indicator_intensity_min;
+ int indicator_intensity_step;
+ int torch_intensity_min;
+ int torch_intensity_step;
+ int flash_intensity_min;
+ int flash_intensity_step;
+ int flash_timeout_min;
+ int flash_timeout_step;
+};
+
+#define fled_to_fl(__fled) container_of(__fled, struct fl, fled)
+#define iled_cdev_to_fl(__iled_cdev) \
+ container_of(__iled_cdev, struct fl, iled_cdev)
+
+/* Return negative errno else zero on success */
+static int fl_write(struct fl *flash, u8 addr, u8 val)
+{
+ struct i2c_client *client = flash->client;
+ int rval;
+
+ rval = i2c_smbus_write_byte_data(client, addr, val);
+
+ dev_dbg(&client->dev, "Write Addr:%02X Val:%02X %s\n", addr, val,
+ rval < 0 ? "fail" : "ok");
+
+ return rval;
+}
+
+/* Return negative errno else a data byte received from the device. */
+static int fl_read(struct fl *flash, u8 addr)
+{
+ struct i2c_client *client = flash->client;
+ int rval;
+
+ rval = i2c_smbus_read_byte_data(client, addr);
+
+ dev_dbg(&client->dev, "Read Addr:%02X Val:%02X %s\n", addr, rval,
+ rval < 0 ? "fail" : "ok");
+
+ return rval;
+}
+
+/* -----------------------------------------------------------------------------
+ * Hardware configuration and trigger
+ */
+
+/**
+ * fl_set_config - Set flash configuration registers
+ * @flash: The flash
+ *
+ * Configure the hardware with flash, assist and indicator currents, as well as
+ * flash timeout.
+ *
+ * Return 0 on success, or a negative error code if an I2C communication error
+ * occurred.
+ */
+static int fl_set_current(struct fl *flash)
+{
+ printk("flash: set_current\n");
+
+ //return fl_write(flash, AS_CURRENT_SET_REG, val);
+ return -EINVAL;
+}
+
+static int fl_set_timeout(struct fl *flash)
+{
+ printk("flash: set_timeout\n");
+
+ //return fl_write(flash, AS_INDICATOR_AND_TIMER_REG, val);
+ return -EINVAL;
+}
+
+/**
+ * fl_set_control - Set flash control register
+ * @flash: The flash
+ * @mode: Desired output mode
+ * @on: Desired output state
+ *
+ * Configure the hardware with output mode and state.
+ *
+ * Return 0 on success, or a negative error code if an I2C communication error
+ * occurred.
+ */
+static int
+fl_set_control(struct fl *flash, enum as_mode mode, bool on)
+{
+ printk("flash: set_control\n");
+
+ //return fl_write(flash, AS_CONTROL_REG, reg);
+}
+
+static int __fl_get_fault(struct fl *flash, u32 *fault)
+{
+ printk("flash: get_fault\n");
+ return 0;
+}
+
+static int fl_get_fault(struct led_classdev_flash *fled, u32 *fault)
+{
+ struct fl *flash = fled_to_fl(fled);
+
+ return __fl_get_fault(flash, fault);
+}
+
+static unsigned int __fl_current_to_reg(unsigned int min, unsigned int max,
+ unsigned int step,
+ unsigned int val)
+{
+ if (val < min)
+ val = min;
+
+ if (val > max)
+ val = max;
+
+ return (val - min) / step;
+}
+
+static unsigned int fl_current_to_reg(struct fl *flash, bool is_flash,
+ unsigned int ua)
+{
+ if (is_flash)
+ return __fl_current_to_reg(flash->torch_intensity_min,
+ flash->cfg.assist_max_ua,
+ flash->torch_intensity_step, ua);
+ else
+ return __fl_current_to_reg(flash->flash_intensity_min,
+ flash->cfg.flash_max_ua,
+ flash->flash_intensity_step, ua);
+}
+
+static int fl_set_indicator_brightness(struct led_classdev *iled_cdev,
+ enum led_brightness brightness)
+{
+ struct fl *flash = iled_cdev_to_fl(iled_cdev);
+ int rval;
+
+ flash->indicator_current = brightness;
+
+ rval = fl_set_timeout(flash);
+ if (rval)
+ return rval;
+
+ return fl_set_control(flash, AS_MODE_INDICATOR, brightness);
+}
+
+static int fl_set_assist_brightness(struct led_classdev *fled_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled = lcdev_to_flcdev(fled_cdev);
+ struct fl *flash = fled_to_fl(fled);
+ int rval;
+
+ if (brightness) {
+ /* Register value 0 is 20 mA. */
+ flash->assist_current = brightness - 1;
+
+ rval = fl_set_current(flash);
+ if (rval)
+ return rval;
+ }
+
+ return fl_set_control(flash, AS_MODE_ASSIST, brightness);
+}
+
+static int fl_set_flash_brightness(struct led_classdev_flash *fled,
+ u32 brightness_ua)
+{
+ struct fl *flash = fled_to_fl(fled);
+
+ flash->flash_current = fl_current_to_reg(flash, true, brightness_ua);
+
+ return fl_set_current(flash);
+}
+
+static int fl_set_flash_timeout(struct led_classdev_flash *fled,
+ u32 timeout_us)
+{
+ struct fl *flash = fled_to_fl(fled);
+
+ flash->timeout = 0; // FIXME AS_TIMER_US_TO_CODE(timeout_us);
+
+ return fl_set_timeout(flash);
+}
+
+static int fl_set_strobe(struct led_classdev_flash *fled, bool state)
+{
+ struct fl *flash = fled_to_fl(fled);
+
+ return fl_set_control(flash, AS_MODE_FLASH, state);
+}
+
+static const struct led_flash_ops fl_led_flash_ops = {
+ .flash_brightness_set = fl_set_flash_brightness,
+ .timeout_set = fl_set_flash_timeout,
+ .strobe_set = fl_set_strobe,
+ .fault_get = fl_get_fault,
+};
+
+static int fl_setup(struct fl *flash)
+{
+ struct device *dev = &flash->client->dev;
+ u32 fault = 0;
+ int rval;
+
+ /* clear errors */
+ rval = fl_get_fault(flash, &fault);
+ if (rval < 0)
+ return rval;
+
+ dev_dbg(dev, "Fault info: %02x\n", rval);
+
+ rval = fl_set_current(flash);
+ if (rval < 0)
+ return rval;
+
+ rval = fl_set_timeout(flash);
+ if (rval < 0)
+ return rval;
+
+ rval = fl_set_control(flash, AS_MODE_INDICATOR, false);
+ if (rval < 0)
+ return rval;
+
+ /* read status */
+ rval = fl_get_fault(&flash->fled, &fault);
+ if (rval < 0)
+ return rval;
+
+ return rval ? -EIO : 0;
+}
+
+static int fl_detect(struct fl *flash)
+{
+ struct device *dev = &flash->client->dev;
+ int rval, man, model, rfu, version;
+ const char *vendor;
+
+ //return fl_write(flash, AS_BOOST_REG, AS_BOOST_CURRENT_DISABLE);
+}
+
+static int fl_parse_node(struct fl *flash,
+ struct fl_names *names,
+ struct fwnode_handle *fwnode)
+{
+ struct fl_config *cfg = &flash->cfg;
+ struct fwnode_handle *child;
+ const char *name;
+ const char *str;
+ int rval;
+
+ fwnode_for_each_child_node(fwnode, child) {
+ u32 id = 0;
+
+ fwnode_property_read_u32(
+ child, is_of_node(child) ? "reg" : "led", &id);
+
+ switch (id) {
+ case AS_LED_FLASH:
+ flash->flash_node = child;
+ break;
+ case AS_LED_INDICATOR:
+ flash->indicator_node = child;
+ break;
+ default:
+ dev_warn(&flash->client->dev,
+ "unknown LED %u encountered, ignoring\n", id);
+ break;
+ }
+ fwnode_handle_get(child);
+ }
+
+ if (!flash->flash_node) {
+ dev_err(&flash->client->dev, "can't find flash node\n");
+ return -ENODEV;
+ }
+
+ rval = fwnode_property_read_string(flash->flash_node, "label", &name);
+ if (!rval) {
+ strlcpy(names->flash, name, sizeof(names->flash));
+ } else if (is_of_node(fwnode)) {
+ snprintf(names->flash, sizeof(names->flash),
+ "%s:flash", to_of_node(fwnode)->name);
+ } else {
+ dev_err(&flash->client->dev, "flash node has no label!\n");
+ return -EINVAL;
+ }
+
+ rval = fwnode_property_read_u32(flash->flash_node, "flash-timeout-us",
+ &cfg->flash_timeout_us);
+ if (rval < 0) {
+ dev_err(&flash->client->dev,
+ "can't read flash-timeout-us property for flash\n");
+ goto out_err;
+ }
+
+ rval = fwnode_property_read_u32(flash->flash_node, "flash-max-microamp",
+ &cfg->flash_max_ua);
+ if (rval < 0) {
+ dev_err(&flash->client->dev,
+ "can't read flash-max-microamp property for flash\n");
+ goto out_err;
+ }
+
+ rval = fwnode_property_read_u32(flash->flash_node, "led-max-microamp",
+ &cfg->assist_max_ua);
+ if (rval < 0) {
+ dev_err(&flash->client->dev,
+ "can't read led-max-microamp property for flash\n");
+ goto out_err;
+ }
+
+ fwnode_property_read_u32(flash->flash_node, "voltage-reference",
+ &cfg->voltage_reference);
+
+ if (!flash->indicator_node) {
+ dev_warn(&flash->client->dev,
+ "can't find indicator node\n");
+ goto out_err;
+ }
+
+ rval = fwnode_property_read_string(flash->indicator_node, "label", &name);
+ if (!rval) {
+ strlcpy(names->indicator, name, sizeof(names->indicator));
+ } else if (is_of_node(fwnode)) {
+ snprintf(names->indicator, sizeof(names->indicator),
+ "%s:indicator", to_of_node(fwnode)->name);
+ } else {
+ dev_err(&flash->client->dev, "flash node has no label!\n");
+ return -EINVAL;
+ }
+
+ rval = fwnode_property_read_u32(flash->indicator_node, "led-max-microamp",
+ &cfg->indicator_max_ua);
+ if (rval < 0) {
+ dev_err(&flash->client->dev,
+ "can't read led-max-microamp property for indicator\n");
+ goto out_err;
+ }
+
+ return 0;
+
+out_err:
+ fwnode_handle_put(flash->flash_node);
+ fwnode_handle_put(flash->indicator_node);
+
+ return rval;
+}
+
+static int fl_led_class_setup(struct fl *flash,
+ struct fl_names *names)
+{
+ struct led_classdev *fled_cdev = &flash->fled.led_cdev;
+ struct led_classdev *iled_cdev = &flash->iled_cdev;
+ struct led_flash_setting *cfg;
+ int rval;
+
+ iled_cdev->name = names->indicator;
+ iled_cdev->brightness_set_blocking = fl_set_indicator_brightness;
+ iled_cdev->max_brightness =
+ flash->cfg.indicator_max_ua / flash->indicator_intensity_step;
+ iled_cdev->flags = LED_CORE_SUSPENDRESUME;
+
+ rval = led_classdev_register(&flash->client->dev, iled_cdev);
+ if (rval < 0)
+ return rval;
+
+ cfg = &flash->fled.brightness;
+ cfg->min = flash->flash_intensity_min;
+ cfg->max = flash->cfg.flash_max_ua;
+ cfg->step = flash->flash_intensity_step;
+ cfg->val = flash->cfg.flash_max_ua;
+
+ cfg = &flash->fled.timeout;
+ cfg->min = flash->flash_timeout_min;
+ cfg->max = flash->cfg.flash_timeout_us;
+ cfg->step = flash->flash_timeout_step;
+ cfg->val = flash->cfg.flash_timeout_us;
+
+ flash->fled.ops = &fl_led_flash_ops;
+
+ fled_cdev->name = names->flash;
+ fled_cdev->brightness_set_blocking = fl_set_assist_brightness;
+ /* Value 0 is off in LED class. */
+ fled_cdev->max_brightness =
+ fl_current_to_reg(flash, false,
+ flash->cfg.assist_max_ua) + 1;
+ fled_cdev->flags = LED_DEV_CAP_FLASH | LED_CORE_SUSPENDRESUME;
+
+ rval = led_classdev_flash_register(&flash->client->dev, &flash->fled);
+ if (rval) {
+ led_classdev_unregister(iled_cdev);
+ dev_err(&flash->client->dev,
+ "led_classdev_flash_register() failed, error %d\n",
+ rval);
+ }
+
+ return rval;
+}
+
+static int fl_v4l2_setup(struct fl *flash)
+{
+ struct led_classdev_flash *fled = &flash->fled;
+ struct led_classdev *led = &fled->led_cdev;
+ struct v4l2_flash_config cfg = {
+ .intensity = {
+ .min = flash->torch_intensity_min,
+ .max = flash->cfg.assist_max_ua,
+ .step = flash->torch_intensity_step,
+ .val = flash->cfg.assist_max_ua,
+ },
+ };
+ struct v4l2_flash_config cfgind = {
+ .intensity = {
+ .min = flash->indicator_intensity_min,
+ .max = flash->cfg.indicator_max_ua,
+ .step = flash->indicator_intensity_step,
+ .val = flash->cfg.indicator_max_ua,
+ },
+ };
+
+ strlcpy(cfg.dev_name, led->name, sizeof(cfg.dev_name));
+ strlcpy(cfgind.dev_name, flash->iled_cdev.name, sizeof(cfg.dev_name));
+
+ flash->vf = v4l2_flash_init(
+ &flash->client->dev, flash->flash_node, &flash->fled, NULL,
+ &cfg);
+ if (IS_ERR(flash->vf))
+ return PTR_ERR(flash->vf);
+
+ flash->vfind = v4l2_flash_indicator_init(
+ &flash->client->dev, flash->indicator_node, &flash->iled_cdev,
+ &cfgind);
+ if (IS_ERR(flash->vfind)) {
+ v4l2_flash_release(flash->vf);
+ return PTR_ERR(flash->vfind);
+ }
+
+ return 0;
+}
+
+static int fl_probe(struct i2c_client *client)
+{
+ struct fl_names names;
+ struct fl *flash;
+ int rval;
+
+ if (!dev_fwnode(&client->dev))
+ return -ENODEV;
+
+ flash = devm_kzalloc(&client->dev, sizeof(*flash), GFP_KERNEL);
+ if (flash == NULL)
+ return -ENOMEM;
+
+ flash->client = client;
+
+ rval = fl_parse_node(flash, &names, dev_fwnode(&client->dev));
+ if (rval < 0)
+ return rval;
+
+ rval = fl_detect(flash);
+ if (rval < 0)
+ goto out_put_nodes;
+
+ mutex_init(&flash->mutex);
+ i2c_set_clientdata(client, flash);
+
+ rval = fl_setup(flash);
+ if (rval)
+ goto out_mutex_destroy;
+
+ rval = fl_led_class_setup(flash, &names);
+ if (rval)
+ goto out_mutex_destroy;
+
+ rval = fl_v4l2_setup(flash);
+ if (rval)
+ goto out_led_classdev_flash_unregister;
+
+ return 0;
+
+out_led_classdev_flash_unregister:
+ led_classdev_flash_unregister(&flash->fled);
+
+out_mutex_destroy:
+ mutex_destroy(&flash->mutex);
+
+out_put_nodes:
+ fwnode_handle_put(flash->flash_node);
+ fwnode_handle_put(flash->indicator_node);
+
+ return rval;
+}
+
+static int fl_remove(struct i2c_client *client)
+{
+ struct fl *flash = i2c_get_clientdata(client);
+
+ fl_set_control(flash, AS_MODE_EXT_TORCH, false);
+
+ v4l2_flash_release(flash->vf);
+
+ led_classdev_flash_unregister(&flash->fled);
+ led_classdev_unregister(&flash->iled_cdev);
+
+ mutex_destroy(&flash->mutex);
+
+ fwnode_handle_put(flash->flash_node);
+ fwnode_handle_put(flash->indicator_node);
+
+ return 0;
+}
+
+static const struct i2c_device_id fl_id_table[] = {
+ { "fixme", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, fl_id_table);
+
+static const struct of_device_id fl_of_table[] = {
+ { .compatible = "ams,fl" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, fl_of_table);
+
+static struct i2c_driver fl_i2c_driver = {
+ .driver = {
+ .of_match_table = fl_of_table,
+ .name = "fixme",
+ },
+ .probe_new = fl_probe,
+ .remove = fl_remove,
+ .id_table = fl_id_table,
+};
+
+module_i2c_driver(fl_i2c_driver);
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx>");
+MODULE_AUTHOR("Sakari Ailus <sakari.ailus@xxxxxx>");
+MODULE_DESCRIPTION("LED flash driver for FL, LM3555 and their clones");
+MODULE_LICENSE("GPL v2");

--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

Attachment: signature.asc
Description: Digital signature