blob: 010ae1e9c7fbfabee6b26ceb4fe412e9b20b436f [file] [log] [blame]
// 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");