diff options
Diffstat (limited to 'greybus_light.patch')
| -rw-r--r-- | greybus_light.patch | 1366 |
1 files changed, 1366 insertions, 0 deletions
diff --git a/greybus_light.patch b/greybus_light.patch new file mode 100644 index 00000000000000..152c14ce826f62 --- /dev/null +++ b/greybus_light.patch @@ -0,0 +1,1366 @@ +--- + drivers/greybus/light.c | 1359 ++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 1359 insertions(+) + +--- /dev/null ++++ b/drivers/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"); |
