]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
greybus: add pwm protocol driver
authorMatt Porter <mporter@linaro.org>
Thu, 13 Nov 2014 14:14:13 +0000 (09:14 -0500)
committerGreg Kroah-Hartman <greg@kroah.com>
Fri, 14 Nov 2014 21:10:21 +0000 (13:10 -0800)
Add a PWM driver that implements the Greybus PWM protocol.

Signed-off-by: Matt Porter <mporter@linaro.org>
Signed-off-by: Greg Kroah-Hartman <greg@kroah.com>
drivers/staging/greybus/Makefile
drivers/staging/greybus/greybus_manifest.h
drivers/staging/greybus/protocol.c
drivers/staging/greybus/protocol.h
drivers/staging/greybus/pwm-gb.c [new file with mode: 0644]

index bb90895deea6541933eb55dac77abef0bc74d322..7ec70fe6bd5dad9789f61b834d86ae80ba7f64cf 100644 (file)
@@ -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
index 844ab8a745b0c3d1ec55f491bf268e9743ee0e06..a0af9a261aa8e339556a85c3c42511c63641e8bf 100644 (file)
@@ -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,
index 93e0af3c6b4e5563e9e0881d0ad3a36e502ebf6a..8df2b4e7b802fb366aec4093b9f0a75363894069 100644 (file)
@@ -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;
index a236401b48abfc7c433e31d6fec73064d08f1b4d..f57f0db4f8194b7cf9c71ca921f8498a80c9dcd1 100644 (file)
@@ -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 (file)
index 0000000..44a4f64
--- /dev/null
@@ -0,0 +1,531 @@
+/*
+ * PWM Greybus driver.
+ *
+ * Copyright 2014 Google Inc.
+ * Copyright 2014 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pwm.h>
+#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);
+}