From 34c6507ca8e675ca0afa11ca0076d108027b0b2f Mon Sep 17 00:00:00 2001 From: Matt Porter Date: Thu, 13 Nov 2014 09:14:13 -0500 Subject: [PATCH] greybus: add pwm protocol driver Add a PWM driver that implements the Greybus PWM protocol. Signed-off-by: Matt Porter Signed-off-by: Greg Kroah-Hartman --- drivers/staging/greybus/Makefile | 1 + drivers/staging/greybus/greybus_manifest.h | 1 + drivers/staging/greybus/protocol.c | 4 + drivers/staging/greybus/protocol.h | 3 + drivers/staging/greybus/pwm-gb.c | 531 +++++++++++++++++++++++++++++ 5 files changed, 540 insertions(+) create mode 100644 drivers/staging/greybus/pwm-gb.c diff --git a/drivers/staging/greybus/Makefile b/drivers/staging/greybus/Makefile index bb90895..7ec70fe 100644 --- a/drivers/staging/greybus/Makefile +++ b/drivers/staging/greybus/Makefile @@ -11,6 +11,7 @@ greybus-y := core.o \ operation.o \ i2c-gb.o \ gpio-gb.o \ + pwm-gb.o \ sdio-gb.o \ uart-gb.o \ battery-gb.o diff --git a/drivers/staging/greybus/greybus_manifest.h b/drivers/staging/greybus/greybus_manifest.h index 844ab8a..a0af9a2 100644 --- a/drivers/staging/greybus/greybus_manifest.h +++ b/drivers/staging/greybus/greybus_manifest.h @@ -32,6 +32,7 @@ enum greybus_protocol { GREYBUS_PROTOCOL_HID = 0x05, GREYBUS_PROTOCOL_SDIO = 0x06, GREYBUS_PROTOCOL_BATTERY = 0x08, + GREYBUS_PROTOCOL_PWM = 0x09, GREYBUS_PROTOCOL_LED = 0x0e, /* ... */ GREYBUS_PROTOCOL_VENDOR = 0xff, diff --git a/drivers/staging/greybus/protocol.c b/drivers/staging/greybus/protocol.c index 93e0af3..8df2b4e 100644 --- a/drivers/staging/greybus/protocol.c +++ b/drivers/staging/greybus/protocol.c @@ -177,6 +177,10 @@ bool gb_protocol_init(void) pr_err("error initializing i2c protocol\n"); ret = false; } + if (!gb_pwm_protocol_init()) { + pr_err("error initializing pwm protocol\n"); + ret = false; + } if (!gb_uart_protocol_init()) { pr_err("error initializing uart protocol\n"); ret = false; diff --git a/drivers/staging/greybus/protocol.h b/drivers/staging/greybus/protocol.h index a236401..f57f0db 100644 --- a/drivers/staging/greybus/protocol.h +++ b/drivers/staging/greybus/protocol.h @@ -55,6 +55,9 @@ extern void gb_gpio_protocol_exit(void); extern bool gb_i2c_protocol_init(void); extern void gb_i2c_protocol_exit(void); +extern bool gb_pwm_protocol_init(void); +extern void gb_pwm_protocol_exit(void); + extern bool gb_uart_protocol_init(void); extern void gb_uart_protocol_exit(void); diff --git a/drivers/staging/greybus/pwm-gb.c b/drivers/staging/greybus/pwm-gb.c new file mode 100644 index 0000000..44a4f64 --- /dev/null +++ b/drivers/staging/greybus/pwm-gb.c @@ -0,0 +1,531 @@ +/* + * PWM Greybus driver. + * + * Copyright 2014 Google Inc. + * Copyright 2014 Linaro Ltd. + * + * Released under the GPLv2 only. + */ + +#include +#include +#include +#include +#include "greybus.h" + +struct gb_pwm_chip { + struct gb_connection *connection; + u8 version_major; + u8 version_minor; + u8 pwm_max; /* max pwm number */ + + struct pwm_chip chip; + struct pwm_chip *pwm; +}; +#define pwm_chip_to_gb_pwm_chip(chip) \ + container_of(chip, struct gb_pwm_chip, chip) + +/* Version of the Greybus PWM protocol we support */ +#define GB_PWM_VERSION_MAJOR 0x00 +#define GB_PWM_VERSION_MINOR 0x01 + +/* Greybus PWM request types */ +#define GB_PWM_TYPE_INVALID 0x00 +#define GB_PWM_TYPE_PROTOCOL_VERSION 0x01 +#define GB_PWM_TYPE_PWM_COUNT 0x02 +#define GB_PWM_TYPE_ACTIVATE 0x03 +#define GB_PWM_TYPE_DEACTIVATE 0x04 +#define GB_PWM_TYPE_CONFIG 0x05 +#define GB_PWM_TYPE_POLARITY 0x06 +#define GB_PWM_TYPE_ENABLE 0x07 +#define GB_PWM_TYPE_DISABLE 0x08 +#define GB_PWM_TYPE_RESPONSE 0x80 /* OR'd with rest */ + +struct gb_pwm_simple_response { + __u8 status; +}; + +/* version request has no payload */ +struct gb_pwm_proto_version_response { + __u8 status; + __u8 major; + __u8 minor; +}; + +/* pwm count request has no payload */ +struct gb_pwm_count_response { + __u8 status; + __u8 count; +}; + +struct gb_pwm_activate_request { + __u8 which; +}; + +struct gb_pwm_deactivate_request { + __u8 which; +}; + +struct gb_pwm_config_request { + __u8 which; + __u32 duty; + __u32 period; +}; + +struct gb_pwm_polarity_request { + __u8 which; + __u8 polarity; +}; + +struct gb_pwm_enable_request { + __u8 which; +}; + +struct gb_pwm_disable_request { + __u8 which; +}; + +/* + * This request only uses the connection field, and if successful, + * fills in the major and minor protocol version of the target. + */ +static int gb_pwm_proto_version_operation(struct gb_pwm_chip *pwmc) +{ + struct gb_connection *connection = pwmc->connection; + struct gb_operation *operation; + struct gb_pwm_proto_version_response *response; + int ret; + + /* protocol version request has no payload */ + operation = gb_operation_create(connection, + GB_PWM_TYPE_PROTOCOL_VERSION, + 0, sizeof(*response)); + if (!operation) + 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; + } + + response = operation->response_payload; + if (response->status) { + gb_connection_err(connection, "version response %hhu", + response->status); + ret = -EIO; + } else { + if (response->major > GB_PWM_VERSION_MAJOR) { + pr_err("unsupported major version (%hhu > %hhu)\n", + response->major, GB_PWM_VERSION_MAJOR); + ret = -ENOTSUPP; + goto out; + } + pwmc->version_major = response->major; + pwmc->version_minor = response->minor; + } +out: + gb_operation_destroy(operation); + + return ret; +} + +static int gb_pwm_count_operation(struct gb_pwm_chip *pwmc) +{ + struct gb_connection *connection = pwmc->connection; + struct gb_operation *operation; + struct gb_pwm_count_response *response; + int ret; + + /* pwm count request has no payload */ + operation = gb_operation_create(connection, GB_PWM_TYPE_PWM_COUNT, + 0, sizeof(*response)); + if (!operation) + return -ENOMEM; + + /* Synchronous operation--no callback */ + ret = gb_operation_request_send(operation, NULL); + if (ret) { + pr_err("line count operation failed (%d)\n", ret); + goto out; + } + + response = operation->response_payload; + if (response->status) { + gb_connection_err(connection, "pwm count response %hhu", + response->status); + ret = -EIO; + } else + pwmc->pwm_max = response->count; +out: + gb_operation_destroy(operation); + + return ret; +} + +static int gb_pwm_activate_operation(struct gb_pwm_chip *pwmc, + u8 which) +{ + struct gb_connection *connection = pwmc->connection; + struct gb_operation *operation; + struct gb_pwm_activate_request *request; + struct gb_pwm_simple_response *response; + int ret; + + if (which > pwmc->pwm_max) + return -EINVAL; + + /* activate response has no payload */ + operation = gb_operation_create(connection, GB_PWM_TYPE_ACTIVATE, + sizeof(*request), sizeof(*response)); + if (!operation) + return -ENOMEM; + request = operation->request_payload; + request->which = which; + + /* Synchronous operation--no callback */ + ret = gb_operation_request_send(operation, NULL); + if (ret) { + pr_err("activate operation failed (%d)\n", ret); + goto out; + } + + response = operation->response_payload; + if (response->status) { + gb_connection_err(connection, "activate response %hhu", + response->status); + ret = -EIO; + } +out: + gb_operation_destroy(operation); + + return ret; +} + +static int gb_pwm_deactivate_operation(struct gb_pwm_chip *pwmc, + u8 which) +{ + struct gb_connection *connection = pwmc->connection; + struct gb_operation *operation; + struct gb_pwm_deactivate_request *request; + struct gb_pwm_simple_response *response; + int ret; + + if (which > pwmc->pwm_max) + return -EINVAL; + + /* deactivate response has no payload */ + operation = gb_operation_create(connection, GB_PWM_TYPE_DEACTIVATE, + sizeof(*request), sizeof(*response)); + if (!operation) + return -ENOMEM; + request = operation->request_payload; + request->which = which; + + /* Synchronous operation--no callback */ + ret = gb_operation_request_send(operation, NULL); + if (ret) { + pr_err("deactivate operation failed (%d)\n", ret); + goto out; + } + + response = operation->response_payload; + if (response->status) { + gb_connection_err(connection, "deactivate response %hhu", + response->status); + ret = -EIO; + } +out: + gb_operation_destroy(operation); + + return ret; +} + +static int gb_pwm_config_operation(struct gb_pwm_chip *pwmc, + u8 which, u32 duty, u32 period) +{ + struct gb_connection *connection = pwmc->connection; + struct gb_operation *operation; + struct gb_pwm_config_request *request; + struct gb_pwm_simple_response *response; + int ret; + + if (which > pwmc->pwm_max) + return -EINVAL; + + operation = gb_operation_create(connection, GB_PWM_TYPE_CONFIG, + sizeof(*request), sizeof(*response)); + if (!operation) + return -ENOMEM; + request = operation->request_payload; + request->which = which; + request->duty = duty; + request->period = period; + + /* Synchronous operation--no callback */ + ret = gb_operation_request_send(operation, NULL); + if (ret) { + pr_err("config operation failed (%d)\n", ret); + goto out; + } + + response = operation->response_payload; + if (response->status) { + gb_connection_err(connection, "config response %hhu", + response->status); + ret = -EIO; + } +out: + gb_operation_destroy(operation); + + return ret; +} + + +static int gb_pwm_set_polarity_operation(struct gb_pwm_chip *pwmc, + u8 which, u8 polarity) +{ + struct gb_connection *connection = pwmc->connection; + struct gb_operation *operation; + struct gb_pwm_polarity_request *request; + struct gb_pwm_simple_response *response; + int ret; + + if (which > pwmc->pwm_max) + return -EINVAL; + + operation = gb_operation_create(connection, GB_PWM_TYPE_POLARITY, + sizeof(*request), sizeof(*response)); + if (!operation) + return -ENOMEM; + request = operation->request_payload; + request->which = which; + request->polarity = polarity; + + /* Synchronous operation--no callback */ + ret = gb_operation_request_send(operation, NULL); + if (ret) { + pr_err("set polarity operation failed (%d)\n", ret); + goto out; + } + + response = operation->response_payload; + if (response->status) { + gb_connection_err(connection, "set polarity response %hhu", + response->status); + ret = -EIO; + } +out: + gb_operation_destroy(operation); + + return ret; +} + +static int gb_pwm_enable_operation(struct gb_pwm_chip *pwmc, + u8 which) +{ + struct gb_connection *connection = pwmc->connection; + struct gb_operation *operation; + struct gb_pwm_enable_request *request; + struct gb_pwm_simple_response *response; + int ret; + + if (which > pwmc->pwm_max) + return -EINVAL; + + /* enable response has no payload */ + operation = gb_operation_create(connection, GB_PWM_TYPE_ENABLE, + sizeof(*request), sizeof(*response)); + if (!operation) + return -ENOMEM; + request = operation->request_payload; + request->which = which; + + /* Synchronous operation--no callback */ + ret = gb_operation_request_send(operation, NULL); + if (ret) { + pr_err("enable operation failed (%d)\n", ret); + goto out; + } + + response = operation->response_payload; + if (response->status) { + gb_connection_err(connection, "enable response %hhu", + response->status); + ret = -EIO; + } +out: + gb_operation_destroy(operation); + + return ret; +} + +static int gb_pwm_disable_operation(struct gb_pwm_chip *pwmc, + u8 which) +{ + struct gb_connection *connection = pwmc->connection; + struct gb_operation *operation; + struct gb_pwm_disable_request *request; + struct gb_pwm_simple_response *response; + int ret; + + if (which > pwmc->pwm_max) + return -EINVAL; + + /* disable response has no payload */ + operation = gb_operation_create(connection, GB_PWM_TYPE_DISABLE, + sizeof(*request), sizeof(*response)); + if (!operation) + return -ENOMEM; + request = operation->request_payload; + request->which = which; + + /* Synchronous operation--no callback */ + ret = gb_operation_request_send(operation, NULL); + if (ret) { + pr_err("disable operation failed (%d)\n", ret); + goto out; + } + + response = operation->response_payload; + if (response->status) { + gb_connection_err(connection, "disable response %hhu", + response->status); + ret = -EIO; + } +out: + gb_operation_destroy(operation); + + return ret; +} + +static int gb_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct gb_pwm_chip *pwmc = pwm_chip_to_gb_pwm_chip(chip); + + return gb_pwm_activate_operation(pwmc, pwm->hwpwm); +}; + +static void gb_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct gb_pwm_chip *pwmc = pwm_chip_to_gb_pwm_chip(chip); + + if (test_bit(PWMF_ENABLED, &pwm->flags)) + dev_warn(chip->dev, "freeing PWM device without disabling\n"); + + gb_pwm_deactivate_operation(pwmc, pwm->hwpwm); +} + +static int gb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct gb_pwm_chip *pwmc = pwm_chip_to_gb_pwm_chip(chip); + + return gb_pwm_config_operation(pwmc, pwm->hwpwm, duty_ns, period_ns); +}; + +static int gb_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + struct gb_pwm_chip *pwmc = pwm_chip_to_gb_pwm_chip(chip); + + return gb_pwm_set_polarity_operation(pwmc, pwm->hwpwm, polarity); +}; + +static int gb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct gb_pwm_chip *pwmc = pwm_chip_to_gb_pwm_chip(chip); + + return gb_pwm_enable_operation(pwmc, pwm->hwpwm); +}; + +static void gb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct gb_pwm_chip *pwmc = pwm_chip_to_gb_pwm_chip(chip); + + gb_pwm_disable_operation(pwmc, pwm->hwpwm); +}; + +static const struct pwm_ops gb_pwm_ops = { + .request = gb_pwm_request, + .free = gb_pwm_free, + .config = gb_pwm_config, + .set_polarity = gb_pwm_set_polarity, + .enable = gb_pwm_enable, + .disable = gb_pwm_disable, + .owner = THIS_MODULE, +}; + +static int gb_pwm_connection_init(struct gb_connection *connection) +{ + struct gb_pwm_chip *pwmc; + struct pwm_chip *pwm; + int ret; + + pwmc = kzalloc(sizeof(*pwmc), GFP_KERNEL); + if (!pwmc) + return -ENOMEM; + pwmc->connection = connection; + + /* Check for compatible protocol version */ + ret = gb_pwm_proto_version_operation(pwmc); + if (ret) + goto out_err; + + /* Query number of pwms present */ + ret = gb_pwm_count_operation(pwmc); + if (ret) + goto out_err; + + pwm = &pwmc->chip; + + pwm->dev = &connection->dev; + pwm->ops = &gb_pwm_ops; + pwm->base = -1; /* Allocate base dynamically */ + pwm->npwm = pwmc->pwm_max + 1; + pwm->can_sleep = true; /* FIXME */ + + ret = pwmchip_add(pwm); + if (ret) { + pr_err("Failed to register PWM\n"); + return ret; + } + connection->private = pwmc; + + return 0; +out_err: + kfree(pwmc); + return ret; +} + +static void gb_pwm_connection_exit(struct gb_connection *connection) +{ + struct gb_pwm_chip *pwmc = connection->private; + + if (!pwmc) + return; + + pwmchip_remove(&pwmc->chip); + /* kref_put(pwmc->connection) */ + kfree(pwmc); +} + +static struct gb_protocol pwm_protocol = { + .id = GREYBUS_PROTOCOL_PWM, + .major = 0, + .minor = 1, + .connection_init = gb_pwm_connection_init, + .connection_exit = gb_pwm_connection_exit, + .request_recv = NULL, /* no incoming requests */ +}; + +bool gb_pwm_protocol_init(void) +{ + return gb_protocol_register(&pwm_protocol); +} + +void gb_pwm_protocol_exit(void) +{ + gb_protocol_deregister(&pwm_protocol); +} -- 2.7.4