aboutsummaryrefslogtreecommitdiffstats
path: root/greybus_light.patch
diff options
Diffstat (limited to 'greybus_light.patch')
-rw-r--r--greybus_light.patch1366
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");