![Greg Kroah-Hartman](/assets/img/avatar_default.png)
Now that the SPDX tag is in all greybus files, that identifies the license in a specific and legally-defined manner. So the extra GPL text wording can be removed as it is no longer needed at all. This is done on a quest to remove the 700+ different ways that files in the kernel describe the GPL license text. And there's unneeded stuff like the address (sometimes incorrect) for the FSF which is never needed. No copyright headers or other non-license-description text was removed. Cc: Vaibhav Hiremath <hvaibhav.linux@gmail.com> Reviewed-by: Alex Elder <elder@linaro.org> Acked-by: Vaibhav Agarwal <vaibhav.sr@gmail.com> Acked-by: David Lin <dtwlin@gmail.com> Acked-by: Johan Hovold <johan@kernel.org> Acked-by: Viresh Kumar <viresh.kumar@linaro.org> Acked-by: Mark Greer <mgreer@animalcreek.com> Acked-by: Rui Miguel Silva <rmfrfs@gmail.com> Acked-by: "Bryan O'Donoghue" <pure.logic@nexus-software.ie> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1360 lines
33 KiB
C
1360 lines
33 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Greybus Lights protocol driver.
|
|
*
|
|
* Copyright 2015 Google Inc.
|
|
* Copyright 2015 Linaro Ltd.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/leds.h>
|
|
#include <linux/led-class-flash.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.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;
|
|
struct v4l2_flash *v4l2_flash_ind;
|
|
#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;
|
|
|
|
if (!delay_on || !delay_off)
|
|
return -EINVAL;
|
|
|
|
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 = { {0} }, sd_cfg_ind = { {0} };
|
|
struct led_classdev_flash *fled;
|
|
struct led_classdev *iled = NULL;
|
|
struct gb_channel *channel_torch, *channel_ind, *channel_flash;
|
|
|
|
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.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_ind.intensity);
|
|
iled = &channel_ind->fled.led_cdev;
|
|
}
|
|
|
|
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);
|
|
snprintf(sd_cfg_ind.dev_name, sizeof(sd_cfg_ind.dev_name),
|
|
"%s indicator", 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, &v4l2_flash_ops,
|
|
&sd_cfg);
|
|
if (IS_ERR(light->v4l2_flash))
|
|
return PTR_ERR(light->v4l2_flash);
|
|
|
|
if (channel_ind) {
|
|
light->v4l2_flash_ind =
|
|
v4l2_flash_indicator_init(dev, NULL, iled, &sd_cfg_ind);
|
|
if (IS_ERR(light->v4l2_flash_ind)) {
|
|
v4l2_flash_release(light->v4l2_flash);
|
|
return PTR_ERR(light->v4l2_flash_ind);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void gb_lights_light_v4l2_unregister(struct gb_light *light)
|
|
{
|
|
v4l2_flash_release(light->v4l2_flash_ind);
|
|
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);
|
|
kfree(cdev->name);
|
|
cdev->name = NULL;
|
|
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;
|
|
|
|
return gb_lights_channel_flash_config(channel);
|
|
}
|
|
|
|
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 = kcalloc(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 = kcalloc(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");
|