diff options
Diffstat (limited to 'drivers/staging/greybus/light.c')
-rw-r--r-- | drivers/staging/greybus/light.c | 1359 |
1 files changed, 1359 insertions, 0 deletions
diff --git a/drivers/staging/greybus/light.c b/drivers/staging/greybus/light.c new file mode 100644 index 000000000000..b2847feb7e86 --- /dev/null +++ b/drivers/staging/greybus/light.c @@ -0,0 +1,1359 @@ +/* + * Greybus Lights protocol driver. + * + * Copyright 2015 Google Inc. + * Copyright 2015 Linaro Ltd. + * + * Released under the GPLv2 only. + */ + +#include <linux/kernel.h> +#include <linux/leds.h> +#include <linux/led-class-flash.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/version.h> +#include <media/v4l2-flash-led-class.h> + +#include "greybus.h" +#include "greybus_protocols.h" + +#define NAMES_MAX 32 + +struct gb_channel { + u8 id; + u32 flags; + u32 color; + char *color_name; + u8 fade_in; + u8 fade_out; + u32 mode; + char *mode_name; + struct attribute **attrs; + struct attribute_group *attr_group; + const struct attribute_group **attr_groups; + struct led_classdev *led; +#if IS_REACHABLE(CONFIG_LEDS_CLASS_FLASH) + struct led_classdev_flash fled; + struct led_flash_setting intensity_uA; + struct led_flash_setting timeout_us; +#else + struct led_classdev cled; +#endif + struct gb_light *light; + bool is_registered; + bool releasing; + bool strobe_state; + bool active; + struct mutex lock; +}; + +struct gb_light { + u8 id; + char *name; + struct gb_lights *glights; + u32 flags; + u8 channels_count; + struct gb_channel *channels; + bool has_flash; + bool ready; +#if IS_REACHABLE(CONFIG_V4L2_FLASH_LED_CLASS) + struct v4l2_flash *v4l2_flash; +#endif +}; + +struct gb_lights { + struct gb_connection *connection; + u8 lights_count; + struct gb_light *lights; + struct mutex lights_lock; +}; + +static void gb_lights_channel_free(struct gb_channel *channel); + +static struct gb_connection *get_conn_from_channel(struct gb_channel *channel) +{ + return channel->light->glights->connection; +} + +static struct gb_connection *get_conn_from_light(struct gb_light *light) +{ + return light->glights->connection; +} + +static bool is_channel_flash(struct gb_channel *channel) +{ + return !!(channel->mode & (GB_CHANNEL_MODE_FLASH | GB_CHANNEL_MODE_TORCH + | GB_CHANNEL_MODE_INDICATOR)); +} + +#if IS_REACHABLE(CONFIG_LEDS_CLASS_FLASH) +static struct gb_channel *get_channel_from_cdev(struct led_classdev *cdev) +{ + struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(cdev); + + return container_of(fled_cdev, struct gb_channel, fled); +} + +static struct led_classdev *get_channel_cdev(struct gb_channel *channel) +{ + return &channel->fled.led_cdev; +} + +static struct gb_channel *get_channel_from_mode(struct gb_light *light, + u32 mode) +{ + struct gb_channel *channel = NULL; + int i; + + for (i = 0; i < light->channels_count; i++) { + channel = &light->channels[i]; + if (channel && channel->mode == mode) + break; + } + return channel; +} + +static int __gb_lights_flash_intensity_set(struct gb_channel *channel, + u32 intensity) +{ + struct gb_connection *connection = get_conn_from_channel(channel); + struct gb_bundle *bundle = connection->bundle; + struct gb_lights_set_flash_intensity_request req; + int ret; + + if (channel->releasing) + return -ESHUTDOWN; + + ret = gb_pm_runtime_get_sync(bundle); + if (ret < 0) + return ret; + + req.light_id = channel->light->id; + req.channel_id = channel->id; + req.intensity_uA = cpu_to_le32(intensity); + + ret = gb_operation_sync(connection, GB_LIGHTS_TYPE_SET_FLASH_INTENSITY, + &req, sizeof(req), NULL, 0); + + gb_pm_runtime_put_autosuspend(bundle); + + return ret; +} + +static int __gb_lights_flash_brightness_set(struct gb_channel *channel) +{ + u32 intensity; + + /* If the channel is flash we need to get the attached torch channel */ + if (channel->mode & GB_CHANNEL_MODE_FLASH) + channel = get_channel_from_mode(channel->light, + GB_CHANNEL_MODE_TORCH); + + /* For not flash we need to convert brightness to intensity */ + intensity = channel->intensity_uA.min + + (channel->intensity_uA.step * channel->led->brightness); + + return __gb_lights_flash_intensity_set(channel, intensity); +} +#else +static struct gb_channel *get_channel_from_cdev(struct led_classdev *cdev) +{ + return container_of(cdev, struct gb_channel, cled); +} + +static struct led_classdev *get_channel_cdev(struct gb_channel *channel) +{ + return &channel->cled; +} + +static int __gb_lights_flash_brightness_set(struct gb_channel *channel) +{ + return 0; +} +#endif + +static int gb_lights_color_set(struct gb_channel *channel, u32 color); +static int gb_lights_fade_set(struct gb_channel *channel); + +static void led_lock(struct led_classdev *cdev) +{ + mutex_lock(&cdev->led_access); +} + +static void led_unlock(struct led_classdev *cdev) +{ + mutex_unlock(&cdev->led_access); +} + +#define gb_lights_fade_attr(__dir) \ +static ssize_t fade_##__dir##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct led_classdev *cdev = dev_get_drvdata(dev); \ + struct gb_channel *channel = get_channel_from_cdev(cdev); \ + \ + return sprintf(buf, "%u\n", channel->fade_##__dir); \ +} \ + \ +static ssize_t fade_##__dir##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + struct led_classdev *cdev = dev_get_drvdata(dev); \ + struct gb_channel *channel = get_channel_from_cdev(cdev); \ + u8 fade; \ + int ret; \ + \ + led_lock(cdev); \ + if (led_sysfs_is_disabled(cdev)) { \ + ret = -EBUSY; \ + goto unlock; \ + } \ + \ + ret = kstrtou8(buf, 0, &fade); \ + if (ret < 0) { \ + dev_err(dev, "could not parse fade value %d\n", ret); \ + goto unlock; \ + } \ + if (channel->fade_##__dir == fade) \ + goto unlock; \ + channel->fade_##__dir = fade; \ + \ + ret = gb_lights_fade_set(channel); \ + if (ret < 0) \ + goto unlock; \ + \ + ret = size; \ +unlock: \ + led_unlock(cdev); \ + return ret; \ +} \ +static DEVICE_ATTR_RW(fade_##__dir) + +gb_lights_fade_attr(in); +gb_lights_fade_attr(out); + +static ssize_t color_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct gb_channel *channel = get_channel_from_cdev(cdev); + + return sprintf(buf, "0x%08x\n", channel->color); +} + +static ssize_t color_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct gb_channel *channel = get_channel_from_cdev(cdev); + u32 color; + int ret; + + led_lock(cdev); + if (led_sysfs_is_disabled(cdev)) { + ret = -EBUSY; + goto unlock; + } + ret = kstrtou32(buf, 0, &color); + if (ret < 0) { + dev_err(dev, "could not parse color value %d\n", ret); + goto unlock; + } + + ret = gb_lights_color_set(channel, color); + if (ret < 0) + goto unlock; + + channel->color = color; + ret = size; +unlock: + led_unlock(cdev); + return ret; +} +static DEVICE_ATTR_RW(color); + +static int channel_attr_groups_set(struct gb_channel *channel, + struct led_classdev *cdev) +{ + int attr = 0; + int size = 0; + + if (channel->flags & GB_LIGHT_CHANNEL_MULTICOLOR) + size++; + if (channel->flags & GB_LIGHT_CHANNEL_FADER) + size += 2; + + if (!size) + return 0; + + /* Set attributes based in the channel flags */ + channel->attrs = kcalloc(size + 1, sizeof(**channel->attrs), + GFP_KERNEL); + if (!channel->attrs) + return -ENOMEM; + channel->attr_group = kcalloc(1, sizeof(*channel->attr_group), + GFP_KERNEL); + if (!channel->attr_group) + return -ENOMEM; + channel->attr_groups = kcalloc(2, sizeof(*channel->attr_groups), + GFP_KERNEL); + if (!channel->attr_groups) + return -ENOMEM; + + if (channel->flags & GB_LIGHT_CHANNEL_MULTICOLOR) + channel->attrs[attr++] = &dev_attr_color.attr; + if (channel->flags & GB_LIGHT_CHANNEL_FADER) { + channel->attrs[attr++] = &dev_attr_fade_in.attr; + channel->attrs[attr++] = &dev_attr_fade_out.attr; + } + + channel->attr_group->attrs = channel->attrs; + + channel->attr_groups[0] = channel->attr_group; + + cdev->groups = channel->attr_groups; + + return 0; +} + +static int gb_lights_fade_set(struct gb_channel *channel) +{ + struct gb_connection *connection = get_conn_from_channel(channel); + struct gb_bundle *bundle = connection->bundle; + struct gb_lights_set_fade_request req; + int ret; + + if (channel->releasing) + return -ESHUTDOWN; + + ret = gb_pm_runtime_get_sync(bundle); + if (ret < 0) + return ret; + + req.light_id = channel->light->id; + req.channel_id = channel->id; + req.fade_in = channel->fade_in; + req.fade_out = channel->fade_out; + ret = gb_operation_sync(connection, GB_LIGHTS_TYPE_SET_FADE, + &req, sizeof(req), NULL, 0); + + gb_pm_runtime_put_autosuspend(bundle); + + return ret; +} + +static int gb_lights_color_set(struct gb_channel *channel, u32 color) +{ + struct gb_connection *connection = get_conn_from_channel(channel); + struct gb_bundle *bundle = connection->bundle; + struct gb_lights_set_color_request req; + int ret; + + if (channel->releasing) + return -ESHUTDOWN; + + ret = gb_pm_runtime_get_sync(bundle); + if (ret < 0) + return ret; + + req.light_id = channel->light->id; + req.channel_id = channel->id; + req.color = cpu_to_le32(color); + ret = gb_operation_sync(connection, GB_LIGHTS_TYPE_SET_COLOR, + &req, sizeof(req), NULL, 0); + + gb_pm_runtime_put_autosuspend(bundle); + + return ret; +} + +static int __gb_lights_led_brightness_set(struct gb_channel *channel) +{ + struct gb_lights_set_brightness_request req; + struct gb_connection *connection = get_conn_from_channel(channel); + struct gb_bundle *bundle = connection->bundle; + bool old_active; + int ret; + + mutex_lock(&channel->lock); + ret = gb_pm_runtime_get_sync(bundle); + if (ret < 0) + goto out_unlock; + + old_active = channel->active; + + req.light_id = channel->light->id; + req.channel_id = channel->id; + req.brightness = (u8)channel->led->brightness; + + ret = gb_operation_sync(connection, GB_LIGHTS_TYPE_SET_BRIGHTNESS, + &req, sizeof(req), NULL, 0); + if (ret < 0) + goto out_pm_put; + + if (channel->led->brightness) + channel->active = true; + else + channel->active = false; + + /* we need to keep module alive when turning to active state */ + if (!old_active && channel->active) + goto out_unlock; + + /* + * on the other hand if going to inactive we still hold a reference and + * need to put it, so we could go to suspend. + */ + if (old_active && !channel->active) + gb_pm_runtime_put_autosuspend(bundle); + +out_pm_put: + gb_pm_runtime_put_autosuspend(bundle); +out_unlock: + mutex_unlock(&channel->lock); + + return ret; +} + +static int __gb_lights_brightness_set(struct gb_channel *channel) +{ + int ret; + + if (channel->releasing) + return 0; + + if (is_channel_flash(channel)) + ret = __gb_lights_flash_brightness_set(channel); + else + ret = __gb_lights_led_brightness_set(channel); + + return ret; +} + +static int gb_brightness_set(struct led_classdev *cdev, + enum led_brightness value) +{ + struct gb_channel *channel = get_channel_from_cdev(cdev); + + channel->led->brightness = value; + + return __gb_lights_brightness_set(channel); +} + +static enum led_brightness gb_brightness_get(struct led_classdev *cdev) + +{ + struct gb_channel *channel = get_channel_from_cdev(cdev); + + return channel->led->brightness; +} + +static int gb_blink_set(struct led_classdev *cdev, unsigned long *delay_on, + unsigned long *delay_off) +{ + struct gb_channel *channel = get_channel_from_cdev(cdev); + struct gb_connection *connection = get_conn_from_channel(channel); + struct gb_bundle *bundle = connection->bundle; + struct gb_lights_blink_request req; + bool old_active; + int ret; + + if (channel->releasing) + return -ESHUTDOWN; + + mutex_lock(&channel->lock); + ret = gb_pm_runtime_get_sync(bundle); + if (ret < 0) + goto out_unlock; + + old_active = channel->active; + + req.light_id = channel->light->id; + req.channel_id = channel->id; + req.time_on_ms = cpu_to_le16(*delay_on); + req.time_off_ms = cpu_to_le16(*delay_off); + + ret = gb_operation_sync(connection, GB_LIGHTS_TYPE_SET_BLINK, &req, + sizeof(req), NULL, 0); + if (ret < 0) + goto out_pm_put; + + if (delay_on) + channel->active = true; + else + channel->active = false; + + /* we need to keep module alive when turning to active state */ + if (!old_active && channel->active) + goto out_unlock; + + /* + * on the other hand if going to inactive we still hold a reference and + * need to put it, so we could go to suspend. + */ + if (old_active && !channel->active) + gb_pm_runtime_put_autosuspend(bundle); + +out_pm_put: + gb_pm_runtime_put_autosuspend(bundle); +out_unlock: + mutex_unlock(&channel->lock); + + return ret; +} + +static void gb_lights_led_operations_set(struct gb_channel *channel, + struct led_classdev *cdev) +{ + cdev->brightness_get = gb_brightness_get; + cdev->brightness_set_blocking = gb_brightness_set; + + if (channel->flags & GB_LIGHT_CHANNEL_BLINK) + cdev->blink_set = gb_blink_set; +} + +#if IS_REACHABLE(CONFIG_V4L2_FLASH_LED_CLASS) +/* V4L2 specific helpers */ +static const struct v4l2_flash_ops v4l2_flash_ops; + +static void __gb_lights_channel_v4l2_config(struct led_flash_setting *channel_s, + struct led_flash_setting *v4l2_s) +{ + v4l2_s->min = channel_s->min; + v4l2_s->max = channel_s->max; + v4l2_s->step = channel_s->step; + /* For v4l2 val is the default value */ + v4l2_s->val = channel_s->max; +} + +static int gb_lights_light_v4l2_register(struct gb_light *light) +{ + struct gb_connection *connection = get_conn_from_light(light); + struct device *dev = &connection->bundle->dev; + struct v4l2_flash_config *sd_cfg; + struct led_classdev_flash *fled; + struct led_classdev_flash *iled = NULL; + struct gb_channel *channel_torch, *channel_ind, *channel_flash; + int ret = 0; + + sd_cfg = kcalloc(1, sizeof(*sd_cfg), GFP_KERNEL); + if (!sd_cfg) + return -ENOMEM; + + channel_torch = get_channel_from_mode(light, GB_CHANNEL_MODE_TORCH); + if (channel_torch) + __gb_lights_channel_v4l2_config(&channel_torch->intensity_uA, + &sd_cfg->torch_intensity); + + channel_ind = get_channel_from_mode(light, GB_CHANNEL_MODE_INDICATOR); + if (channel_ind) { + __gb_lights_channel_v4l2_config(&channel_ind->intensity_uA, + &sd_cfg->indicator_intensity); + iled = &channel_ind->fled; + } + + channel_flash = get_channel_from_mode(light, GB_CHANNEL_MODE_FLASH); + WARN_ON(!channel_flash); + + fled = &channel_flash->fled; + + snprintf(sd_cfg->dev_name, sizeof(sd_cfg->dev_name), "%s", light->name); + + /* Set the possible values to faults, in our case all faults */ + sd_cfg->flash_faults = LED_FAULT_OVER_VOLTAGE | LED_FAULT_TIMEOUT | + LED_FAULT_OVER_TEMPERATURE | LED_FAULT_SHORT_CIRCUIT | + LED_FAULT_OVER_CURRENT | LED_FAULT_INDICATOR | + LED_FAULT_UNDER_VOLTAGE | LED_FAULT_INPUT_VOLTAGE | + LED_FAULT_LED_OVER_TEMPERATURE; + + light->v4l2_flash = v4l2_flash_init(dev, NULL, fled, iled, + &v4l2_flash_ops, sd_cfg); + if (IS_ERR_OR_NULL(light->v4l2_flash)) { + ret = PTR_ERR(light->v4l2_flash); + goto out_free; + } + + return ret; + +out_free: + kfree(sd_cfg); + return ret; +} + +static void gb_lights_light_v4l2_unregister(struct gb_light *light) +{ + v4l2_flash_release(light->v4l2_flash); +} +#else +static int gb_lights_light_v4l2_register(struct gb_light *light) +{ + struct gb_connection *connection = get_conn_from_light(light); + + dev_err(&connection->bundle->dev, "no support for v4l2 subdevices\n"); + return 0; +} + +static void gb_lights_light_v4l2_unregister(struct gb_light *light) +{ +} +#endif + +#if IS_REACHABLE(CONFIG_LEDS_CLASS_FLASH) +/* Flash specific operations */ +static int gb_lights_flash_intensity_set(struct led_classdev_flash *fcdev, + u32 brightness) +{ + struct gb_channel *channel = container_of(fcdev, struct gb_channel, + fled); + int ret; + + ret = __gb_lights_flash_intensity_set(channel, brightness); + if (ret < 0) + return ret; + + fcdev->brightness.val = brightness; + + return 0; +} + +static int gb_lights_flash_intensity_get(struct led_classdev_flash *fcdev, + u32 *brightness) +{ + *brightness = fcdev->brightness.val; + + return 0; +} + +static int gb_lights_flash_strobe_set(struct led_classdev_flash *fcdev, + bool state) +{ + struct gb_channel *channel = container_of(fcdev, struct gb_channel, + fled); + struct gb_connection *connection = get_conn_from_channel(channel); + struct gb_bundle *bundle = connection->bundle; + struct gb_lights_set_flash_strobe_request req; + int ret; + + if (channel->releasing) + return -ESHUTDOWN; + + ret = gb_pm_runtime_get_sync(bundle); + if (ret < 0) + return ret; + + req.light_id = channel->light->id; + req.channel_id = channel->id; + req.state = state ? 1 : 0; + + ret = gb_operation_sync(connection, GB_LIGHTS_TYPE_SET_FLASH_STROBE, + &req, sizeof(req), NULL, 0); + if (!ret) + channel->strobe_state = state; + + gb_pm_runtime_put_autosuspend(bundle); + + return ret; +} + +static int gb_lights_flash_strobe_get(struct led_classdev_flash *fcdev, + bool *state) +{ + struct gb_channel *channel = container_of(fcdev, struct gb_channel, + fled); + + *state = channel->strobe_state; + return 0; +} + +static int gb_lights_flash_timeout_set(struct led_classdev_flash *fcdev, + u32 timeout) +{ + struct gb_channel *channel = container_of(fcdev, struct gb_channel, + fled); + struct gb_connection *connection = get_conn_from_channel(channel); + struct gb_bundle *bundle = connection->bundle; + struct gb_lights_set_flash_timeout_request req; + int ret; + + if (channel->releasing) + return -ESHUTDOWN; + + ret = gb_pm_runtime_get_sync(bundle); + if (ret < 0) + return ret; + + req.light_id = channel->light->id; + req.channel_id = channel->id; + req.timeout_us = cpu_to_le32(timeout); + + ret = gb_operation_sync(connection, GB_LIGHTS_TYPE_SET_FLASH_TIMEOUT, + &req, sizeof(req), NULL, 0); + if (!ret) + fcdev->timeout.val = timeout; + + gb_pm_runtime_put_autosuspend(bundle); + + return ret; +} + +static int gb_lights_flash_fault_get(struct led_classdev_flash *fcdev, + u32 *fault) +{ + struct gb_channel *channel = container_of(fcdev, struct gb_channel, + fled); + struct gb_connection *connection = get_conn_from_channel(channel); + struct gb_bundle *bundle = connection->bundle; + struct gb_lights_get_flash_fault_request req; + struct gb_lights_get_flash_fault_response resp; + int ret; + + if (channel->releasing) + return -ESHUTDOWN; + + ret = gb_pm_runtime_get_sync(bundle); + if (ret < 0) + return ret; + + req.light_id = channel->light->id; + req.channel_id = channel->id; + + ret = gb_operation_sync(connection, GB_LIGHTS_TYPE_GET_FLASH_FAULT, + &req, sizeof(req), &resp, sizeof(resp)); + if (!ret) + *fault = le32_to_cpu(resp.fault); + + gb_pm_runtime_put_autosuspend(bundle); + + return ret; +} + +static const struct led_flash_ops gb_lights_flash_ops = { + .flash_brightness_set = gb_lights_flash_intensity_set, + .flash_brightness_get = gb_lights_flash_intensity_get, + .strobe_set = gb_lights_flash_strobe_set, + .strobe_get = gb_lights_flash_strobe_get, + .timeout_set = gb_lights_flash_timeout_set, + .fault_get = gb_lights_flash_fault_get, +}; + +static int __gb_lights_channel_torch_attach(struct gb_channel *channel, + struct gb_channel *channel_torch) +{ + char *name; + + /* we can only attach torch to a flash channel */ + if (!(channel->mode & GB_CHANNEL_MODE_FLASH)) + return 0; + + /* Move torch brightness to the destination */ + channel->led->max_brightness = channel_torch->led->max_brightness; + + /* append mode name to flash name */ + name = kasprintf(GFP_KERNEL, "%s_%s", channel->led->name, + channel_torch->mode_name); + if (!name) + return -ENOMEM; + kfree(channel->led->name); + channel->led->name = name; + + channel_torch->led = channel->led; + + return 0; +} + +static int __gb_lights_flash_led_register(struct gb_channel *channel) +{ + struct gb_connection *connection = get_conn_from_channel(channel); + struct led_classdev_flash *fled = &channel->fled; + struct led_flash_setting *fset; + struct gb_channel *channel_torch; + int ret; + + fled->ops = &gb_lights_flash_ops; + + fled->led_cdev.flags |= LED_DEV_CAP_FLASH; + + fset = &fled->brightness; + fset->min = channel->intensity_uA.min; + fset->max = channel->intensity_uA.max; + fset->step = channel->intensity_uA.step; + fset->val = channel->intensity_uA.max; + + /* Only the flash mode have the timeout constraints settings */ + if (channel->mode & GB_CHANNEL_MODE_FLASH) { + fset = &fled->timeout; + fset->min = channel->timeout_us.min; + fset->max = channel->timeout_us.max; + fset->step = channel->timeout_us.step; + fset->val = channel->timeout_us.max; + } + + /* + * If light have torch mode channel, this channel will be the led + * classdev of the registered above flash classdev + */ + channel_torch = get_channel_from_mode(channel->light, + GB_CHANNEL_MODE_TORCH); + if (channel_torch) { + ret = __gb_lights_channel_torch_attach(channel, channel_torch); + if (ret < 0) + goto fail; + } + + ret = led_classdev_flash_register(&connection->bundle->dev, fled); + if (ret < 0) + goto fail; + + channel->is_registered = true; + return 0; +fail: + channel->led = NULL; + return ret; +} + +static void __gb_lights_flash_led_unregister(struct gb_channel *channel) +{ + if (!channel->is_registered) + return; + + led_classdev_flash_unregister(&channel->fled); +} + +static int gb_lights_channel_flash_config(struct gb_channel *channel) +{ + struct gb_connection *connection = get_conn_from_channel(channel); + struct gb_lights_get_channel_flash_config_request req; + struct gb_lights_get_channel_flash_config_response conf; + struct led_flash_setting *fset; + int ret; + + req.light_id = channel->light->id; + req.channel_id = channel->id; + + ret = gb_operation_sync(connection, + GB_LIGHTS_TYPE_GET_CHANNEL_FLASH_CONFIG, + &req, sizeof(req), &conf, sizeof(conf)); + if (ret < 0) + return ret; + + /* + * Intensity constraints for flash related modes: flash, torch, + * indicator. They will be needed for v4l2 registration. + */ + fset = &channel->intensity_uA; + fset->min = le32_to_cpu(conf.intensity_min_uA); + fset->max = le32_to_cpu(conf.intensity_max_uA); + fset->step = le32_to_cpu(conf.intensity_step_uA); + + /* + * On flash type, max brightness is set as the number of intensity steps + * available. + */ + channel->led->max_brightness = (fset->max - fset->min) / fset->step; + + /* Only the flash mode have the timeout constraints settings */ + if (channel->mode & GB_CHANNEL_MODE_FLASH) { + fset = &channel->timeout_us; + fset->min = le32_to_cpu(conf.timeout_min_us); + fset->max = le32_to_cpu(conf.timeout_max_us); + fset->step = le32_to_cpu(conf.timeout_step_us); + } + + return 0; +} +#else +static int gb_lights_channel_flash_config(struct gb_channel *channel) +{ + struct gb_connection *connection = get_conn_from_channel(channel); + + dev_err(&connection->bundle->dev, "no support for flash devices\n"); + return 0; +} + +static int __gb_lights_flash_led_register(struct gb_channel *channel) +{ + return 0; +} + +static void __gb_lights_flash_led_unregister(struct gb_channel *channel) +{ +} + +#endif + +static int __gb_lights_led_register(struct gb_channel *channel) +{ + struct gb_connection *connection = get_conn_from_channel(channel); + struct led_classdev *cdev = get_channel_cdev(channel); + int ret; + + ret = led_classdev_register(&connection->bundle->dev, cdev); + if (ret < 0) + channel->led = NULL; + else + channel->is_registered = true; + return ret; +} + +static int gb_lights_channel_register(struct gb_channel *channel) +{ + /* Normal LED channel, just register in led classdev and we are done */ + if (!is_channel_flash(channel)) + return __gb_lights_led_register(channel); + + /* + * Flash Type need more work, register flash classdev, indicator as + * flash classdev, torch will be led classdev of the flash classdev. + */ + if (!(channel->mode & GB_CHANNEL_MODE_TORCH)) + return __gb_lights_flash_led_register(channel); + + return 0; +} + +static void __gb_lights_led_unregister(struct gb_channel *channel) +{ + struct led_classdev *cdev = get_channel_cdev(channel); + + if (!channel->is_registered) + return; + + led_classdev_unregister(cdev); + channel->led = NULL; +} + +static void gb_lights_channel_unregister(struct gb_channel *channel) +{ + /* The same as register, handle channels differently */ + if (!is_channel_flash(channel)) { + __gb_lights_led_unregister(channel); + return; + } + + if (channel->mode & GB_CHANNEL_MODE_TORCH) + __gb_lights_led_unregister(channel); + else + __gb_lights_flash_led_unregister(channel); +} + +static int gb_lights_channel_config(struct gb_light *light, + struct gb_channel *channel) +{ + struct gb_lights_get_channel_config_response conf; + struct gb_lights_get_channel_config_request req; + struct gb_connection *connection = get_conn_from_light(light); + struct led_classdev *cdev = get_channel_cdev(channel); + char *name; + int ret; + + req.light_id = light->id; + req.channel_id = channel->id; + + ret = gb_operation_sync(connection, GB_LIGHTS_TYPE_GET_CHANNEL_CONFIG, + &req, sizeof(req), &conf, sizeof(conf)); + if (ret < 0) + return ret; + + channel->light = light; + channel->mode = le32_to_cpu(conf.mode); + channel->flags = le32_to_cpu(conf.flags); + channel->color = le32_to_cpu(conf.color); + channel->color_name = kstrndup(conf.color_name, NAMES_MAX, GFP_KERNEL); + if (!channel->color_name) + return -ENOMEM; + channel->mode_name = kstrndup(conf.mode_name, NAMES_MAX, GFP_KERNEL); + if (!channel->mode_name) + return -ENOMEM; + + channel->led = cdev; + + name = kasprintf(GFP_KERNEL, "%s:%s:%s", light->name, + channel->color_name, channel->mode_name); + if (!name) + return -ENOMEM; + + cdev->name = name; + + cdev->max_brightness = conf.max_brightness; + + ret = channel_attr_groups_set(channel, cdev); + if (ret < 0) + return ret; + + gb_lights_led_operations_set(channel, cdev); + + /* + * If it is not a flash related channel (flash, torch or indicator) we + * are done here. If not, continue and fetch flash related + * configurations. + */ + if (!is_channel_flash(channel)) + return ret; + + light->has_flash = true; + + ret = gb_lights_channel_flash_config(channel); + if (ret < 0) + return ret; + + return ret; +} + +static int gb_lights_light_config(struct gb_lights *glights, u8 id) +{ + struct gb_light *light = &glights->lights[id]; + struct gb_lights_get_light_config_request req; + struct gb_lights_get_light_config_response conf; + int ret; + int i; + + light->glights = glights; + light->id = id; + + req.id = id; + + ret = gb_operation_sync(glights->connection, + GB_LIGHTS_TYPE_GET_LIGHT_CONFIG, + &req, sizeof(req), &conf, sizeof(conf)); + if (ret < 0) + return ret; + + if (!conf.channel_count) + return -EINVAL; + if (!strlen(conf.name)) + return -EINVAL; + + light->channels_count = conf.channel_count; + light->name = kstrndup(conf.name, NAMES_MAX, GFP_KERNEL); + + light->channels = kzalloc(light->channels_count * + sizeof(struct gb_channel), GFP_KERNEL); + if (!light->channels) + return -ENOMEM; + + /* First we collect all the configurations for all channels */ + for (i = 0; i < light->channels_count; i++) { + light->channels[i].id = i; + ret = gb_lights_channel_config(light, &light->channels[i]); + if (ret < 0) + return ret; + } + + return 0; +} + +static int gb_lights_light_register(struct gb_light *light) +{ + int ret; + int i; + + /* + * Then, if everything went ok in getting configurations, we register + * the classdev, flash classdev and v4l2 subsystem, if a flash device is + * found. + */ + for (i = 0; i < light->channels_count; i++) { + ret = gb_lights_channel_register(&light->channels[i]); + if (ret < 0) + return ret; + + mutex_init(&light->channels[i].lock); + } + + light->ready = true; + + if (light->has_flash) { + ret = gb_lights_light_v4l2_register(light); + if (ret < 0) { + light->has_flash = false; + return ret; + } + } + + return 0; +} + +static void gb_lights_channel_free(struct gb_channel *channel) +{ + kfree(channel->attrs); + kfree(channel->attr_group); + kfree(channel->attr_groups); + kfree(channel->color_name); + kfree(channel->mode_name); + mutex_destroy(&channel->lock); +} + +static void gb_lights_channel_release(struct gb_channel *channel) +{ + channel->releasing = true; + + gb_lights_channel_unregister(channel); + + gb_lights_channel_free(channel); +} + +static void gb_lights_light_release(struct gb_light *light) +{ + int i; + int count; + + light->ready = false; + + count = light->channels_count; + + if (light->has_flash) + gb_lights_light_v4l2_unregister(light); + + for (i = 0; i < count; i++) { + gb_lights_channel_release(&light->channels[i]); + light->channels_count--; + } + kfree(light->channels); + kfree(light->name); +} + +static void gb_lights_release(struct gb_lights *glights) +{ + int i; + + if (!glights) + return; + + mutex_lock(&glights->lights_lock); + if (!glights->lights) + goto free_glights; + + for (i = 0; i < glights->lights_count; i++) + gb_lights_light_release(&glights->lights[i]); + + kfree(glights->lights); + +free_glights: + mutex_unlock(&glights->lights_lock); + mutex_destroy(&glights->lights_lock); + kfree(glights); +} + +static int gb_lights_get_count(struct gb_lights *glights) +{ + struct gb_lights_get_lights_response resp; + int ret; + + ret = gb_operation_sync(glights->connection, GB_LIGHTS_TYPE_GET_LIGHTS, + NULL, 0, &resp, sizeof(resp)); + if (ret < 0) + return ret; + + if (!resp.lights_count) + return -EINVAL; + + glights->lights_count = resp.lights_count; + + return 0; +} + +static int gb_lights_create_all(struct gb_lights *glights) +{ + struct gb_connection *connection = glights->connection; + int ret; + int i; + + mutex_lock(&glights->lights_lock); + ret = gb_lights_get_count(glights); + if (ret < 0) + goto out; + + glights->lights = kzalloc(glights->lights_count * + sizeof(struct gb_light), GFP_KERNEL); + if (!glights->lights) { + ret = -ENOMEM; + goto out; + } + + for (i = 0; i < glights->lights_count; i++) { + ret = gb_lights_light_config(glights, i); + if (ret < 0) { + dev_err(&connection->bundle->dev, + "Fail to configure lights device\n"); + goto out; + } + } + +out: + mutex_unlock(&glights->lights_lock); + return ret; +} + +static int gb_lights_register_all(struct gb_lights *glights) +{ + struct gb_connection *connection = glights->connection; + int ret = 0; + int i; + + mutex_lock(&glights->lights_lock); + for (i = 0; i < glights->lights_count; i++) { + ret = gb_lights_light_register(&glights->lights[i]); + if (ret < 0) { + dev_err(&connection->bundle->dev, + "Fail to enable lights device\n"); + break; + } + } + + mutex_unlock(&glights->lights_lock); + return ret; +} + +static int gb_lights_request_handler(struct gb_operation *op) +{ + struct gb_connection *connection = op->connection; + struct device *dev = &connection->bundle->dev; + struct gb_lights *glights = gb_connection_get_data(connection); + struct gb_light *light; + struct gb_message *request; + struct gb_lights_event_request *payload; + int ret = 0; + u8 light_id; + u8 event; + + if (op->type != GB_LIGHTS_TYPE_EVENT) { + dev_err(dev, "Unsupported unsolicited event: %u\n", op->type); + return -EINVAL; + } + + request = op->request; + + if (request->payload_size < sizeof(*payload)) { + dev_err(dev, "Wrong event size received (%zu < %zu)\n", + request->payload_size, sizeof(*payload)); + return -EINVAL; + } + + payload = request->payload; + light_id = payload->light_id; + + if (light_id >= glights->lights_count || + !glights->lights[light_id].ready) { + dev_err(dev, "Event received for unconfigured light id: %d\n", + light_id); + return -EINVAL; + } + + event = payload->event; + + if (event & GB_LIGHTS_LIGHT_CONFIG) { + light = &glights->lights[light_id]; + + mutex_lock(&glights->lights_lock); + gb_lights_light_release(light); + ret = gb_lights_light_config(glights, light_id); + if (!ret) + ret = gb_lights_light_register(light); + if (ret < 0) + gb_lights_light_release(light); + mutex_unlock(&glights->lights_lock); + } + + return ret; +} + +static int gb_lights_probe(struct gb_bundle *bundle, + const struct greybus_bundle_id *id) +{ + struct greybus_descriptor_cport *cport_desc; + struct gb_connection *connection; + struct gb_lights *glights; + int ret; + + if (bundle->num_cports != 1) + return -ENODEV; + + cport_desc = &bundle->cport_desc[0]; + if (cport_desc->protocol_id != GREYBUS_PROTOCOL_LIGHTS) + return -ENODEV; + + glights = kzalloc(sizeof(*glights), GFP_KERNEL); + if (!glights) + return -ENOMEM; + + mutex_init(&glights->lights_lock); + + connection = gb_connection_create(bundle, le16_to_cpu(cport_desc->id), + gb_lights_request_handler); + if (IS_ERR(connection)) { + ret = PTR_ERR(connection); + goto out; + } + + glights->connection = connection; + gb_connection_set_data(connection, glights); + + greybus_set_drvdata(bundle, glights); + + /* We aren't ready to receive an incoming request yet */ + ret = gb_connection_enable_tx(connection); + if (ret) + goto error_connection_destroy; + + /* + * Setup all the lights devices over this connection, if anything goes + * wrong tear down all lights + */ + ret = gb_lights_create_all(glights); + if (ret < 0) + goto error_connection_disable; + + /* We are ready to receive an incoming request now, enable RX as well */ + ret = gb_connection_enable(connection); + if (ret) + goto error_connection_disable; + + /* Enable & register lights */ + ret = gb_lights_register_all(glights); + if (ret < 0) + goto error_connection_disable; + + gb_pm_runtime_put_autosuspend(bundle); + + return 0; + +error_connection_disable: + gb_connection_disable(connection); +error_connection_destroy: + gb_connection_destroy(connection); +out: + gb_lights_release(glights); + return ret; +} + +static void gb_lights_disconnect(struct gb_bundle *bundle) +{ + struct gb_lights *glights = greybus_get_drvdata(bundle); + + if (gb_pm_runtime_get_sync(bundle)) + gb_pm_runtime_get_noresume(bundle); + + gb_connection_disable(glights->connection); + gb_connection_destroy(glights->connection); + + gb_lights_release(glights); +} + +static const struct greybus_bundle_id gb_lights_id_table[] = { + { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_LIGHTS) }, + { } +}; +MODULE_DEVICE_TABLE(greybus, gb_lights_id_table); + +static struct greybus_driver gb_lights_driver = { + .name = "lights", + .probe = gb_lights_probe, + .disconnect = gb_lights_disconnect, + .id_table = gb_lights_id_table, +}; +module_greybus_driver(gb_lights_driver); + +MODULE_LICENSE("GPL v2"); |