/* * Generic pwmlib implementation * * Copyright (C) 2011 Sascha Hauer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include struct pwm_device { struct pwm_chip *chip; const char *label; unsigned long flags; #define FLAG_REQUESTED 0 #define FLAG_ENABLED 1 struct list_head node; }; static LIST_HEAD(pwm_list); static DEFINE_MUTEX(pwm_lock); static struct pwm_device *_find_pwm(int pwm_id) { struct pwm_device *pwm; list_for_each_entry(pwm, &pwm_list, node) { if (pwm->chip->pwm_id == pwm_id) return pwm; } return NULL; } /** * pwmchip_add() - register a new PWM chip * @chip: the PWM chip to add */ int pwmchip_add(struct pwm_chip *chip) { struct pwm_device *pwm; int ret = 0; pwm = kzalloc(sizeof(*pwm), GFP_KERNEL); if (!pwm) return -ENOMEM; pwm->chip = chip; mutex_lock(&pwm_lock); if (chip->pwm_id >= 0 && _find_pwm(chip->pwm_id)) { ret = -EBUSY; goto out; } list_add_tail(&pwm->node, &pwm_list); out: mutex_unlock(&pwm_lock); if (ret) kfree(pwm); return ret; } EXPORT_SYMBOL_GPL(pwmchip_add); /** * pwmchip_remove() - remove a PWM chip * @chip: the PWM chip to remove * * Removes a PWM chip. This function may return busy if the PWM chip provides * a PWM device that is still requested. */ int pwmchip_remove(struct pwm_chip *chip) { struct pwm_device *pwm; int ret = 0; mutex_lock(&pwm_lock); pwm = _find_pwm(chip->pwm_id); if (!pwm) { ret = -ENOENT; goto out; } if (test_bit(FLAG_REQUESTED, &pwm->flags)) { ret = -EBUSY; goto out; } list_del(&pwm->node); kfree(pwm); out: mutex_unlock(&pwm_lock); return ret; } EXPORT_SYMBOL_GPL(pwmchip_remove); /** * pwm_request() - request a PWM device * @pwm_id: global PWM device index * @label: PWM device label */ struct pwm_device *pwm_request(int pwm_id, const char *label) { struct pwm_device *pwm; int ret; mutex_lock(&pwm_lock); pwm = _find_pwm(pwm_id); if (!pwm) { pwm = ERR_PTR(-ENOENT); goto out; } if (test_bit(FLAG_REQUESTED, &pwm->flags)) { pwm = ERR_PTR(-EBUSY); goto out; } if (!try_module_get(pwm->chip->ops->owner)) { pwm = ERR_PTR(-ENODEV); goto out; } if (pwm->chip->ops->request) { ret = pwm->chip->ops->request(pwm->chip); if (ret) { pwm = ERR_PTR(ret); goto out_put; } } pwm->label = label; set_bit(FLAG_REQUESTED, &pwm->flags); goto out; out_put: module_put(pwm->chip->ops->owner); out: mutex_unlock(&pwm_lock); return pwm; } EXPORT_SYMBOL_GPL(pwm_request); /** * pwm_free() - free a PWM device * @pwm: PWM device */ void pwm_free(struct pwm_device *pwm) { mutex_lock(&pwm_lock); if (!test_and_clear_bit(FLAG_REQUESTED, &pwm->flags)) { pr_warning("PWM device already freed\n"); goto out; } pwm->label = NULL; module_put(pwm->chip->ops->owner); out: mutex_unlock(&pwm_lock); } EXPORT_SYMBOL_GPL(pwm_free); /** * pwm_config() - change a PWM device configuration * @pwm: PWM device * @duty_ns: "on" time (in nanoseconds) * @period_ns: duration (in nanoseconds) of one cycle */ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) { return pwm->chip->ops->config(pwm->chip, duty_ns, period_ns); } EXPORT_SYMBOL_GPL(pwm_config); /** * pwm_enable() - start a PWM output toggling * @pwm: PWM device */ int pwm_enable(struct pwm_device *pwm) { if (!test_and_set_bit(FLAG_ENABLED, &pwm->flags)) return pwm->chip->ops->enable(pwm->chip); return 0; } EXPORT_SYMBOL_GPL(pwm_enable); /** * pwm_disable() - stop a PWM output toggling * @pwm: PWM device */ void pwm_disable(struct pwm_device *pwm) { if (test_and_clear_bit(FLAG_ENABLED, &pwm->flags)) pwm->chip->ops->disable(pwm->chip); } EXPORT_SYMBOL_GPL(pwm_disable);