From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S935235AbcIPOUz (ORCPT ); Fri, 16 Sep 2016 10:20:55 -0400 Received: from mail.linuxfoundation.org ([140.211.169.12]:51797 "EHLO mail.linuxfoundation.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S935188AbcIPOUn (ORCPT ); Fri, 16 Sep 2016 10:20:43 -0400 Date: Fri, 16 Sep 2016 16:20:47 +0200 From: Greg KH To: Arnd Bergmann , linux-kernel@vger.kernel.org Cc: Johan Hovold , Rui Miguel Silva , Laurent Pinchart , Sandeep Patil , Matt Porter , John Stultz , Rob Herring , Viresh Kumar , Alex Elder , David Lin , "Bryan O'Donoghue" , Vaibhav Agarwal , Mark Greer Subject: [patch 25/32] greybus: bridged phy pwm driver Message-ID: <20160916142047.GE2040@kroah.com> References: <20160914100949.GA6179@kroah.com> <20160916064058.GA17821@kroah.com> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20160916064058.GA17821@kroah.com> User-Agent: Mutt/1.7.0 (2016-08-17) Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org This driver implements the Greybus bridged phy pwm class protocol. Signed-off-by: Greg Kroah-Hartman --- drivers/greybus/pwm.c | 338 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 338 insertions(+) --- /dev/null +++ b/drivers/greybus/pwm.c @@ -0,0 +1,338 @@ +/* + * PWM Greybus driver. + * + * Copyright 2014 Google Inc. + * Copyright 2014 Linaro Ltd. + * + * Released under the GPLv2 only. + */ + +#include +#include +#include +#include + +#include "greybus.h" +#include "gbphy.h" + +struct gb_pwm_chip { + struct gb_connection *connection; + 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) + + +static int gb_pwm_count_operation(struct gb_pwm_chip *pwmc) +{ + struct gb_pwm_count_response response; + int ret; + + ret = gb_operation_sync(pwmc->connection, GB_PWM_TYPE_PWM_COUNT, + NULL, 0, &response, sizeof(response)); + if (ret) + return ret; + pwmc->pwm_max = response.count; + return 0; +} + +static int gb_pwm_activate_operation(struct gb_pwm_chip *pwmc, + u8 which) +{ + struct gb_pwm_activate_request request; + struct gbphy_device *gbphy_dev; + int ret; + + if (which > pwmc->pwm_max) + return -EINVAL; + + request.which = which; + + gbphy_dev = to_gbphy_dev(pwmc->chip.dev); + ret = gbphy_runtime_get_sync(gbphy_dev); + if (ret) + return ret; + + ret = gb_operation_sync(pwmc->connection, GB_PWM_TYPE_ACTIVATE, + &request, sizeof(request), NULL, 0); + + gbphy_runtime_put_autosuspend(gbphy_dev); + + return ret; +} + +static int gb_pwm_deactivate_operation(struct gb_pwm_chip *pwmc, + u8 which) +{ + struct gb_pwm_deactivate_request request; + struct gbphy_device *gbphy_dev; + int ret; + + if (which > pwmc->pwm_max) + return -EINVAL; + + request.which = which; + + gbphy_dev = to_gbphy_dev(pwmc->chip.dev); + ret = gbphy_runtime_get_sync(gbphy_dev); + if (ret) + return ret; + + ret = gb_operation_sync(pwmc->connection, GB_PWM_TYPE_DEACTIVATE, + &request, sizeof(request), NULL, 0); + + gbphy_runtime_put_autosuspend(gbphy_dev); + + return ret; +} + +static int gb_pwm_config_operation(struct gb_pwm_chip *pwmc, + u8 which, u32 duty, u32 period) +{ + struct gb_pwm_config_request request; + struct gbphy_device *gbphy_dev; + int ret; + + if (which > pwmc->pwm_max) + return -EINVAL; + + request.which = which; + request.duty = cpu_to_le32(duty); + request.period = cpu_to_le32(period); + + gbphy_dev = to_gbphy_dev(pwmc->chip.dev); + ret = gbphy_runtime_get_sync(gbphy_dev); + if (ret) + return ret; + + ret = gb_operation_sync(pwmc->connection, GB_PWM_TYPE_CONFIG, + &request, sizeof(request), NULL, 0); + + gbphy_runtime_put_autosuspend(gbphy_dev); + + return ret; +} + +static int gb_pwm_set_polarity_operation(struct gb_pwm_chip *pwmc, + u8 which, u8 polarity) +{ + struct gb_pwm_polarity_request request; + struct gbphy_device *gbphy_dev; + int ret; + + if (which > pwmc->pwm_max) + return -EINVAL; + + request.which = which; + request.polarity = polarity; + + gbphy_dev = to_gbphy_dev(pwmc->chip.dev); + ret = gbphy_runtime_get_sync(gbphy_dev); + if (ret) + return ret; + + ret = gb_operation_sync(pwmc->connection, GB_PWM_TYPE_POLARITY, + &request, sizeof(request), NULL, 0); + + gbphy_runtime_put_autosuspend(gbphy_dev); + + return ret; +} + +static int gb_pwm_enable_operation(struct gb_pwm_chip *pwmc, + u8 which) +{ + struct gb_pwm_enable_request request; + struct gbphy_device *gbphy_dev; + int ret; + + if (which > pwmc->pwm_max) + return -EINVAL; + + request.which = which; + + gbphy_dev = to_gbphy_dev(pwmc->chip.dev); + ret = gbphy_runtime_get_sync(gbphy_dev); + if (ret) + return ret; + + ret = gb_operation_sync(pwmc->connection, GB_PWM_TYPE_ENABLE, + &request, sizeof(request), NULL, 0); + if (ret) + gbphy_runtime_put_autosuspend(gbphy_dev); + + return ret; +} + +static int gb_pwm_disable_operation(struct gb_pwm_chip *pwmc, + u8 which) +{ + struct gb_pwm_disable_request request; + struct gbphy_device *gbphy_dev; + int ret; + + if (which > pwmc->pwm_max) + return -EINVAL; + + request.which = which; + + ret = gb_operation_sync(pwmc->connection, GB_PWM_TYPE_DISABLE, + &request, sizeof(request), NULL, 0); + + gbphy_dev = to_gbphy_dev(pwmc->chip.dev); + gbphy_runtime_put_autosuspend(gbphy_dev); + + 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 (pwm_is_enabled(pwm)) + 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_probe(struct gbphy_device *gbphy_dev, + const struct gbphy_device_id *id) +{ + 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; + + connection = gb_connection_create(gbphy_dev->bundle, + le16_to_cpu(gbphy_dev->cport_desc->id), + NULL); + if (IS_ERR(connection)) { + ret = PTR_ERR(connection); + goto exit_pwmc_free; + } + + pwmc->connection = connection; + gb_connection_set_data(connection, pwmc); + gb_gbphy_set_data(gbphy_dev, pwmc); + + ret = gb_connection_enable(connection); + if (ret) + goto exit_connection_destroy; + + /* Query number of pwms present */ + ret = gb_pwm_count_operation(pwmc); + if (ret) + goto exit_connection_disable; + + pwm = &pwmc->chip; + + pwm->dev = &gbphy_dev->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) { + dev_err(&gbphy_dev->dev, + "failed to register PWM: %d\n", ret); + goto exit_connection_disable; + } + + gbphy_runtime_put_autosuspend(gbphy_dev); + return 0; + +exit_connection_disable: + gb_connection_disable(connection); +exit_connection_destroy: + gb_connection_destroy(connection); +exit_pwmc_free: + kfree(pwmc); + return ret; +} + +static void gb_pwm_remove(struct gbphy_device *gbphy_dev) +{ + struct gb_pwm_chip *pwmc = gb_gbphy_get_data(gbphy_dev); + struct gb_connection *connection = pwmc->connection; + int ret; + + ret = gbphy_runtime_get_sync(gbphy_dev); + if (ret) + gbphy_runtime_get_noresume(gbphy_dev); + + pwmchip_remove(&pwmc->chip); + gb_connection_disable(connection); + gb_connection_destroy(connection); + kfree(pwmc); +} + +static const struct gbphy_device_id gb_pwm_id_table[] = { + { GBPHY_PROTOCOL(GREYBUS_PROTOCOL_PWM) }, + { }, +}; +MODULE_DEVICE_TABLE(gbphy, gb_pwm_id_table); + +static struct gbphy_driver pwm_driver = { + .name = "pwm", + .probe = gb_pwm_probe, + .remove = gb_pwm_remove, + .id_table = gb_pwm_id_table, +}; + +module_gbphy_driver(pwm_driver); +MODULE_LICENSE("GPL v2");