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