diff --git a/drivers/staging/greybus/Makefile b/drivers/staging/greybus/Makefile index 7ec70fe6bd5d..198539cd0a06 100644 --- a/drivers/staging/greybus/Makefile +++ b/drivers/staging/greybus/Makefile @@ -14,7 +14,8 @@ greybus-y := core.o \ pwm-gb.o \ sdio-gb.o \ uart-gb.o \ - battery-gb.o + battery-gb.o \ + vibrator-gb.o obj-m += greybus.o obj-m += es1-ap-usb.o diff --git a/drivers/staging/greybus/kernel_ver.h b/drivers/staging/greybus/kernel_ver.h index e4cda5ce65fa..ca0da11e4d8d 100644 --- a/drivers/staging/greybus/kernel_ver.h +++ b/drivers/staging/greybus/kernel_ver.h @@ -18,6 +18,18 @@ struct device_attribute dev_attr_##_name = __ATTR_RO(_name) #endif +#ifndef DEVICE_ATTR_WO +#define DEVICE_ATTR_WO(_name) \ + struct device_attribute dev_attr_##_name = __ATTR_WO(_name) +#endif + +#ifndef __ATTR_WO +#define __ATTR_WO(_name) { \ + .attr = { .name = __stringify(_name), .mode = S_IWUSR }, \ + .store = _name##_store, \ +} +#endif + #ifndef U8_MAX #define U8_MAX ((u8)~0U) #endif /* ! U8_MAX */ diff --git a/drivers/staging/greybus/protocol.c b/drivers/staging/greybus/protocol.c index 8df2b4e7b802..346120f8d4e3 100644 --- a/drivers/staging/greybus/protocol.c +++ b/drivers/staging/greybus/protocol.c @@ -189,11 +189,16 @@ bool gb_protocol_init(void) pr_err("error initializing sdio protocol\n"); ret = false; } + if (!gb_vibrator_protocol_init()) { + pr_err("error initializing vibrator protocol\n"); + ret = false; + } return ret; } void gb_protocol_exit(void) { + gb_vibrator_protocol_exit(); gb_sdio_protocol_exit(); gb_uart_protocol_exit(); gb_i2c_protocol_exit(); diff --git a/drivers/staging/greybus/protocol.h b/drivers/staging/greybus/protocol.h index 1aeb34068a3f..69a40079a2c5 100644 --- a/drivers/staging/greybus/protocol.h +++ b/drivers/staging/greybus/protocol.h @@ -64,6 +64,9 @@ extern void gb_uart_protocol_exit(void); extern bool gb_sdio_protocol_init(void); extern void gb_sdio_protocol_exit(void); +extern bool gb_vibrator_protocol_init(void); +extern void gb_vibrator_protocol_exit(void); + bool gb_protocol_init(void); void gb_protocol_exit(void); diff --git a/drivers/staging/greybus/vibrator-gb.c b/drivers/staging/greybus/vibrator-gb.c new file mode 100644 index 000000000000..2fcb2a45c254 --- /dev/null +++ b/drivers/staging/greybus/vibrator-gb.c @@ -0,0 +1,298 @@ +/* + * I2C bridge driver for the Greybus "generic" I2C module. + * + * Copyright 2014 Google Inc. + * + * Released under the GPLv2 only. + */ + +#include +#include +#include +#include +#include +#include "greybus.h" + +struct gb_vibrator_device { + struct gb_connection *connection; + struct device *dev; + u8 version_major; + u8 version_minor; +}; + +/* Version of the Greybus i2c protocol we support */ +#define GB_VIBRATOR_VERSION_MAJOR 0x00 +#define GB_VIBRATOR_VERSION_MINOR 0x01 + +/* Greybus Vibrator request types */ +#define GB_VIBRATOR_TYPE_INVALID 0x00 +#define GB_VIBRATOR_TYPE_PROTOCOL_VERSION 0x01 +#define GB_VIBRATOR_TYPE_ON 0x02 +#define GB_VIBRATOR_TYPE_OFF 0x03 +#define GB_VIBRATOR_TYPE_RESPONSE 0x80 /* OR'd with rest */ + +struct gb_vibrator_proto_version_response { + __u8 status; + __u8 major; + __u8 minor; +}; + +struct gb_vibrator_on_request { + __le16 timeout_ms; +}; + +struct gb_vibrator_simple_response { + __u8 status; +}; + +static int request_operation(struct gb_connection *connection, int type, + void *response, int response_size) +{ + struct gb_operation *operation; + struct gb_vibrator_simple_response *fake_request; + u8 *local_response; + int ret; + + local_response = kmalloc(response_size, GFP_KERNEL); + if (!local_response) + return -ENOMEM; + + operation = gb_operation_create(connection, type, 0, response_size); + if (!operation) { + kfree(local_response); + return -ENOMEM; + } + + /* Synchronous operation--no callback */ + ret = gb_operation_request_send(operation, NULL); + if (ret) { + pr_err("version operation failed (%d)\n", ret); + goto out; + } + + /* + * We only want to look at the status, and all requests have the same + * layout for where the status is, so cast this to a random request so + * we can see the status easier. + */ + fake_request = (struct gb_vibrator_simple_response *)local_response; + if (fake_request->status) { + gb_connection_err(connection, "response %hhu", + fake_request->status); + ret = -EIO; + } else { + /* Good request, so copy to the caller's buffer */ + if (response_size && response) + memcpy(response, local_response, response_size); + } +out: + gb_operation_destroy(operation); + kfree(local_response); + + return ret; +} + +/* + * This request only uses the connection field, and if successful, + * fills in the major and minor protocol version of the target. + */ +static int get_version(struct gb_vibrator_device *vib) +{ + struct gb_connection *connection = vib->connection; + struct gb_vibrator_proto_version_response version_request; + int retval; + + retval = request_operation(connection, + GB_VIBRATOR_TYPE_PROTOCOL_VERSION, + &version_request, sizeof(version_request)); + if (retval) + return retval; + + if (version_request.major > GB_VIBRATOR_VERSION_MAJOR) { + dev_err(&connection->dev, + "unsupported major version (%hhu > %hhu)\n", + version_request.major, GB_VIBRATOR_VERSION_MAJOR); + return -ENOTSUPP; + } + + vib->version_major = version_request.major; + vib->version_minor = version_request.minor; + return 0; +} + +static int turn_on(struct gb_vibrator_device *vib, u16 timeout_ms) +{ + struct gb_connection *connection = vib->connection; + struct gb_operation *operation; + struct gb_vibrator_on_request *request; + struct gb_vibrator_simple_response *response; + int retval; + + operation = gb_operation_create(connection, GB_VIBRATOR_TYPE_ON, + sizeof(*request), sizeof(*response)); + if (!operation) + return -ENOMEM; + request = operation->request_payload; + request->timeout_ms = cpu_to_le16(timeout_ms); + + /* Synchronous operation--no callback */ + retval = gb_operation_request_send(operation, NULL); + if (retval) { + dev_err(&connection->dev, + "send data operation failed (%d)\n", retval); + goto out; + } + + response = operation->response_payload; + if (response->status) { + gb_connection_err(connection, "send data response %hhu", + response->status); + retval = -EIO; + } +out: + gb_operation_destroy(operation); + + return retval; + + return 0; +} + +static int turn_off(struct gb_vibrator_device *vib) +{ + struct gb_connection *connection = vib->connection; + int retval; + + retval = request_operation(connection, GB_VIBRATOR_TYPE_OFF, NULL, 0); + if (retval) + return retval; + + return 0; +} + +static ssize_t timeout_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gb_vibrator_device *vib = dev_get_drvdata(dev); + unsigned long val; + int retval; + + retval = kstrtoul(buf, 10, &val); + if (retval < 0) { + dev_err(dev, "could not parse timeout value %d\n", retval); + return retval; + } + + if (val < 0) + return -EINVAL; + if (val) + retval = turn_on(vib, (u16)val); + else + retval = turn_off(vib); + if (retval) + return retval; + + return count; +} +static DEVICE_ATTR_WO(timeout); + +static struct attribute *vibrator_attrs[] = { + &dev_attr_timeout.attr, + NULL, +}; +ATTRIBUTE_GROUPS(vibrator); + +static struct class vibrator_class = { + .name = "vibrator", + .owner = THIS_MODULE, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,11,0) + .dev_groups = vibrator_groups, +#endif +}; + +static int minor; + +static int gb_vibrator_connection_init(struct gb_connection *connection) +{ + struct gb_vibrator_device *vib; + struct device *dev; + int retval; + + vib = kzalloc(sizeof(*vib), GFP_KERNEL); + if (!vib) + return -ENOMEM; + + vib->connection = connection; + + retval = get_version(vib); + if (retval) + goto error; + + /* + * FIXME: for now we create a device in sysfs for the vibrator, but odds + * are there is a "real" device somewhere in the kernel for this, but I + * can't find it at the moment... + */ + dev = device_create(&vibrator_class, NULL, MKDEV(0, 0), vib, + "vibrator%d", minor); + if (IS_ERR(dev)) { + retval = -EINVAL; + goto error; + } + minor++; + vib->dev = dev; + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(3,11,0) + /* + * Newer kernels handle this in a race-free manner, for us, we need + * to "open code this :( + */ + retval = sysfs_create_group(&dev->kobj, vibrator_groups[0]); + if (retval) { + device_unregister(dev); + goto error; + } +#endif + + return 0; + +error: + kfree(vib); + return retval; +} + +static void gb_vibrator_connection_exit(struct gb_connection *connection) +{ + struct gb_vibrator_device *vib = connection->private; + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(3,11,0) + sysfs_remove_group(&vib->dev->kobj, vibrator_groups[0]); +#endif + device_unregister(vib->dev); + kfree(vib); +} + +static struct gb_protocol vibrator_protocol = { + .id = GREYBUS_PROTOCOL_VIBRATOR, + .major = 0, + .minor = 1, + .connection_init = gb_vibrator_connection_init, + .connection_exit = gb_vibrator_connection_exit, + .request_recv = NULL, /* no incoming requests */ +}; + +bool gb_vibrator_protocol_init(void) +{ + int retval; + + retval = class_register(&vibrator_class); + if (retval) + return retval; + + return gb_protocol_register(&vibrator_protocol); +} + +void gb_vibrator_protocol_exit(void) +{ + gb_protocol_deregister(&vibrator_protocol); + class_unregister(&vibrator_class); +}