Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

204
drivers/pwm/Kconfig Normal file
View File

@@ -0,0 +1,204 @@
menuconfig PWM
bool "Pulse-Width Modulation (PWM) Support"
help
Generic Pulse-Width Modulation (PWM) support.
In Pulse-Width Modulation, a variation of the width of pulses
in a rectangular pulse signal is used as a means to alter the
average power of the signal. Applications include efficient
power delivery and voltage regulation. In computer systems,
PWMs are commonly used to control fans or the brightness of
display backlights.
This framework provides a generic interface to PWM devices
within the Linux kernel. On the driver side it provides an API
to register and unregister a PWM chip, an abstraction of a PWM
controller, that supports one or more PWM devices. Client
drivers can request PWM devices and use the generic framework
to configure as well as enable and disable them.
This generic framework replaces the legacy PWM framework which
allows only a single driver implementing the required API. Not
all legacy implementations have been ported to the framework
yet. The framework provides an API that is backward compatible
with the legacy framework so that existing client drivers
continue to work as expected.
If unsure, say no.
if PWM
config PWM_AB8500
tristate "AB8500 PWM support"
depends on AB8500_CORE && ARCH_U8500
help
Generic PWM framework driver for Analog Baseband AB8500.
To compile this driver as a module, choose M here: the module
will be called pwm-ab8500.
config PWM_ATMEL_TCB
tristate "Atmel TC Block PWM support"
depends on ATMEL_TCLIB && OF
help
Generic PWM framework driver for Atmel Timer Counter Block.
A Timer Counter Block provides 6 PWM devices grouped by 2.
Devices in a given group must have the same period.
To compile this driver as a module, choose M here: the module
will be called pwm-atmel-tcb.
config PWM_BFIN
tristate "Blackfin PWM support"
depends on BFIN_GPTIMERS
help
Generic PWM framework driver for Blackfin.
To compile this driver as a module, choose M here: the module
will be called pwm-bfin.
config PWM_IMX
tristate "i.MX PWM support"
depends on ARCH_MXC
help
Generic PWM framework driver for i.MX.
To compile this driver as a module, choose M here: the module
will be called pwm-imx.
config PWM_JZ4740
tristate "Ingenic JZ4740 PWM support"
depends on MACH_JZ4740
help
Generic PWM framework driver for Ingenic JZ4740 based
machines.
To compile this driver as a module, choose M here: the module
will be called pwm-jz4740.
config PWM_LPC32XX
tristate "LPC32XX PWM support"
depends on ARCH_LPC32XX
help
Generic PWM framework driver for LPC32XX. The LPC32XX SOC has two
PWM controllers.
To compile this driver as a module, choose M here: the module
will be called pwm-lpc32xx.
config PWM_MXS
tristate "Freescale MXS PWM support"
depends on ARCH_MXS && OF
select STMP_DEVICE
help
Generic PWM framework driver for Freescale MXS.
To compile this driver as a module, choose M here: the module
will be called pwm-mxs.
config PWM_PUV3
tristate "PKUnity NetBook-0916 PWM support"
depends on ARCH_PUV3
help
Generic PWM framework driver for PKUnity NetBook-0916.
To compile this driver as a module, choose M here: the module
will be called pwm-puv3.
config PWM_PXA
tristate "PXA PWM support"
depends on ARCH_PXA
help
Generic PWM framework driver for PXA.
To compile this driver as a module, choose M here: the module
will be called pwm-pxa.
config PWM_SAMSUNG
tristate "Samsung PWM support"
depends on PLAT_SAMSUNG
help
Generic PWM framework driver for Samsung.
To compile this driver as a module, choose M here: the module
will be called pwm-samsung.
config PWM_SPEAR
tristate "STMicroelectronics SPEAr PWM support"
depends on PLAT_SPEAR
depends on OF
help
Generic PWM framework driver for the PWM controller on ST
SPEAr SoCs.
To compile this driver as a module, choose M here: the module
will be called pwm-spear.
config PWM_TEGRA
tristate "NVIDIA Tegra PWM support"
depends on ARCH_TEGRA
help
Generic PWM framework driver for the PWFM controller found on NVIDIA
Tegra SoCs.
To compile this driver as a module, choose M here: the module
will be called pwm-tegra.
config PWM_TIECAP
tristate "ECAP PWM support"
depends on SOC_AM33XX || ARCH_DAVINCI_DA8XX
help
PWM driver support for the ECAP APWM controller found on AM33XX
TI SOC
To compile this driver as a module, choose M here: the module
will be called pwm-tiecap.
config PWM_TIEHRPWM
tristate "EHRPWM PWM support"
depends on SOC_AM33XX || ARCH_DAVINCI_DA8XX
help
PWM driver support for the EHRPWM controller found on AM33XX
TI SOC
To compile this driver as a module, choose M here: the module
will be called pwm-tiehrpwm.
config PWM_TIPWMSS
bool
default y if SOC_AM33XX && (PWM_TIECAP || PWM_TIEHRPWM)
help
PWM Subsystem driver support for AM33xx SOC.
PWM submodules require PWM config space access from submodule
drivers and require common parent driver support.
config PWM_TWL
tristate "TWL4030/6030 PWM support"
depends on TWL4030_CORE
help
Generic PWM framework driver for TWL4030/6030.
To compile this driver as a module, choose M here: the module
will be called pwm-twl.
config PWM_TWL_LED
tristate "TWL4030/6030 PWM support for LED drivers"
depends on TWL4030_CORE
help
Generic PWM framework driver for TWL4030/6030 LED terminals.
To compile this driver as a module, choose M here: the module
will be called pwm-twl-led.
config PWM_VT8500
tristate "vt8500 PWM support"
depends on ARCH_VT8500
help
Generic PWM framework driver for vt8500.
To compile this driver as a module, choose M here: the module
will be called pwm-vt8500.
endif

19
drivers/pwm/Makefile Normal file
View File

@@ -0,0 +1,19 @@
obj-$(CONFIG_PWM) += core.o
obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o
obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o
obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o
obj-$(CONFIG_PWM_IMX) += pwm-imx.o
obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o
obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o
obj-$(CONFIG_PWM_MXS) += pwm-mxs.o
obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o
obj-$(CONFIG_PWM_PXA) += pwm-pxa.o
obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o
obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o
obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o
obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o
obj-$(CONFIG_PWM_TIPWMSS) += pwm-tipwmss.o
obj-$(CONFIG_PWM_TWL) += pwm-twl.o
obj-$(CONFIG_PWM_TWL_LED) += pwm-twl-led.o
obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o

866
drivers/pwm/core.c Normal file
View File

@@ -0,0 +1,866 @@
/*
* Generic pwmlib implementation
*
* Copyright (C) 2011 Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2011-2012 Avionic Design GmbH
*
* 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 <linux/module.h>
#include <linux/pwm.h>
#include <linux/radix-tree.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#define MAX_PWMS 1024
/* flags in the third cell of the DT PWM specifier */
#define PWM_SPEC_POLARITY (1 << 0)
static DEFINE_MUTEX(pwm_lookup_lock);
static LIST_HEAD(pwm_lookup_list);
static DEFINE_MUTEX(pwm_lock);
static LIST_HEAD(pwm_chips);
static DECLARE_BITMAP(allocated_pwms, MAX_PWMS);
static RADIX_TREE(pwm_tree, GFP_KERNEL);
static struct pwm_device *pwm_to_device(unsigned int pwm)
{
return radix_tree_lookup(&pwm_tree, pwm);
}
static int alloc_pwms(int pwm, unsigned int count)
{
unsigned int from = 0;
unsigned int start;
if (pwm >= MAX_PWMS)
return -EINVAL;
if (pwm >= 0)
from = pwm;
start = bitmap_find_next_zero_area(allocated_pwms, MAX_PWMS, from,
count, 0);
if (pwm >= 0 && start != pwm)
return -EEXIST;
if (start + count > MAX_PWMS)
return -ENOSPC;
return start;
}
static void free_pwms(struct pwm_chip *chip)
{
unsigned int i;
for (i = 0; i < chip->npwm; i++) {
struct pwm_device *pwm = &chip->pwms[i];
radix_tree_delete(&pwm_tree, pwm->pwm);
}
bitmap_clear(allocated_pwms, chip->base, chip->npwm);
kfree(chip->pwms);
chip->pwms = NULL;
}
static struct pwm_chip *pwmchip_find_by_name(const char *name)
{
struct pwm_chip *chip;
if (!name)
return NULL;
mutex_lock(&pwm_lock);
list_for_each_entry(chip, &pwm_chips, list) {
const char *chip_name = dev_name(chip->dev);
if (chip_name && strcmp(chip_name, name) == 0) {
mutex_unlock(&pwm_lock);
return chip;
}
}
mutex_unlock(&pwm_lock);
return NULL;
}
static int pwm_device_request(struct pwm_device *pwm, const char *label)
{
int err;
if (test_bit(PWMF_REQUESTED, &pwm->flags))
return -EBUSY;
if (!try_module_get(pwm->chip->ops->owner))
return -ENODEV;
if (pwm->chip->ops->request) {
err = pwm->chip->ops->request(pwm->chip, pwm);
if (err) {
module_put(pwm->chip->ops->owner);
return err;
}
}
set_bit(PWMF_REQUESTED, &pwm->flags);
pwm->label = label;
return 0;
}
struct pwm_device *
of_pwm_xlate_with_flags(struct pwm_chip *pc, const struct of_phandle_args *args)
{
struct pwm_device *pwm;
if (pc->of_pwm_n_cells < 3)
return ERR_PTR(-EINVAL);
if (args->args[0] >= pc->npwm)
return ERR_PTR(-EINVAL);
pwm = pwm_request_from_chip(pc, args->args[0], NULL);
if (IS_ERR(pwm))
return pwm;
pwm_set_period(pwm, args->args[1]);
if (args->args[2] & PWM_SPEC_POLARITY)
pwm_set_polarity(pwm, PWM_POLARITY_INVERSED);
else
pwm_set_polarity(pwm, PWM_POLARITY_NORMAL);
return pwm;
}
EXPORT_SYMBOL_GPL(of_pwm_xlate_with_flags);
static struct pwm_device *
of_pwm_simple_xlate(struct pwm_chip *pc, const struct of_phandle_args *args)
{
struct pwm_device *pwm;
if (pc->of_pwm_n_cells < 2)
return ERR_PTR(-EINVAL);
if (args->args[0] >= pc->npwm)
return ERR_PTR(-EINVAL);
pwm = pwm_request_from_chip(pc, args->args[0], NULL);
if (IS_ERR(pwm))
return pwm;
pwm_set_period(pwm, args->args[1]);
return pwm;
}
static void of_pwmchip_add(struct pwm_chip *chip)
{
if (!chip->dev || !chip->dev->of_node)
return;
if (!chip->of_xlate) {
chip->of_xlate = of_pwm_simple_xlate;
chip->of_pwm_n_cells = 2;
}
of_node_get(chip->dev->of_node);
}
static void of_pwmchip_remove(struct pwm_chip *chip)
{
if (chip->dev && chip->dev->of_node)
of_node_put(chip->dev->of_node);
}
/**
* pwm_set_chip_data() - set private chip data for a PWM
* @pwm: PWM device
* @data: pointer to chip-specific data
*/
int pwm_set_chip_data(struct pwm_device *pwm, void *data)
{
if (!pwm)
return -EINVAL;
pwm->chip_data = data;
return 0;
}
EXPORT_SYMBOL_GPL(pwm_set_chip_data);
/**
* pwm_get_chip_data() - get private chip data for a PWM
* @pwm: PWM device
*/
void *pwm_get_chip_data(struct pwm_device *pwm)
{
return pwm ? pwm->chip_data : NULL;
}
EXPORT_SYMBOL_GPL(pwm_get_chip_data);
/**
* pwmchip_add() - register a new PWM chip
* @chip: the PWM chip to add
*
* Register a new PWM chip. If chip->base < 0 then a dynamically assigned base
* will be used.
*/
int pwmchip_add(struct pwm_chip *chip)
{
struct pwm_device *pwm;
unsigned int i;
int ret;
if (!chip || !chip->dev || !chip->ops || !chip->ops->config ||
!chip->ops->enable || !chip->ops->disable)
return -EINVAL;
mutex_lock(&pwm_lock);
ret = alloc_pwms(chip->base, chip->npwm);
if (ret < 0)
goto out;
chip->pwms = kzalloc(chip->npwm * sizeof(*pwm), GFP_KERNEL);
if (!chip->pwms) {
ret = -ENOMEM;
goto out;
}
chip->base = ret;
for (i = 0; i < chip->npwm; i++) {
pwm = &chip->pwms[i];
pwm->chip = chip;
pwm->pwm = chip->base + i;
pwm->hwpwm = i;
radix_tree_insert(&pwm_tree, pwm->pwm, pwm);
}
bitmap_set(allocated_pwms, chip->base, chip->npwm);
INIT_LIST_HEAD(&chip->list);
list_add(&chip->list, &pwm_chips);
ret = 0;
if (IS_ENABLED(CONFIG_OF))
of_pwmchip_add(chip);
out:
mutex_unlock(&pwm_lock);
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)
{
unsigned int i;
int ret = 0;
mutex_lock(&pwm_lock);
for (i = 0; i < chip->npwm; i++) {
struct pwm_device *pwm = &chip->pwms[i];
if (test_bit(PWMF_REQUESTED, &pwm->flags)) {
ret = -EBUSY;
goto out;
}
}
list_del_init(&chip->list);
if (IS_ENABLED(CONFIG_OF))
of_pwmchip_remove(chip);
free_pwms(chip);
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
*
* This function is deprecated, use pwm_get() instead.
*/
struct pwm_device *pwm_request(int pwm, const char *label)
{
struct pwm_device *dev;
int err;
if (pwm < 0 || pwm >= MAX_PWMS)
return ERR_PTR(-EINVAL);
mutex_lock(&pwm_lock);
dev = pwm_to_device(pwm);
if (!dev) {
dev = ERR_PTR(-EPROBE_DEFER);
goto out;
}
err = pwm_device_request(dev, label);
if (err < 0)
dev = ERR_PTR(err);
out:
mutex_unlock(&pwm_lock);
return dev;
}
EXPORT_SYMBOL_GPL(pwm_request);
/**
* pwm_request_from_chip() - request a PWM device relative to a PWM chip
* @chip: PWM chip
* @index: per-chip index of the PWM to request
* @label: a literal description string of this PWM
*
* Returns the PWM at the given index of the given PWM chip. A negative error
* code is returned if the index is not valid for the specified PWM chip or
* if the PWM device cannot be requested.
*/
struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip,
unsigned int index,
const char *label)
{
struct pwm_device *pwm;
int err;
if (!chip || index >= chip->npwm)
return ERR_PTR(-EINVAL);
mutex_lock(&pwm_lock);
pwm = &chip->pwms[index];
err = pwm_device_request(pwm, label);
if (err < 0)
pwm = ERR_PTR(err);
mutex_unlock(&pwm_lock);
return pwm;
}
EXPORT_SYMBOL_GPL(pwm_request_from_chip);
/**
* pwm_free() - free a PWM device
* @pwm: PWM device
*
* This function is deprecated, use pwm_put() instead.
*/
void pwm_free(struct pwm_device *pwm)
{
pwm_put(pwm);
}
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)
{
if (!pwm || duty_ns < 0 || period_ns <= 0 || duty_ns > period_ns)
return -EINVAL;
return pwm->chip->ops->config(pwm->chip, pwm, duty_ns, period_ns);
}
EXPORT_SYMBOL_GPL(pwm_config);
/**
* pwm_set_polarity() - configure the polarity of a PWM signal
* @pwm: PWM device
* @polarity: new polarity of the PWM signal
*
* Note that the polarity cannot be configured while the PWM device is enabled
*/
int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity)
{
if (!pwm || !pwm->chip->ops)
return -EINVAL;
if (!pwm->chip->ops->set_polarity)
return -ENOSYS;
if (test_bit(PWMF_ENABLED, &pwm->flags))
return -EBUSY;
return pwm->chip->ops->set_polarity(pwm->chip, pwm, polarity);
}
EXPORT_SYMBOL_GPL(pwm_set_polarity);
/**
* pwm_enable() - start a PWM output toggling
* @pwm: PWM device
*/
int pwm_enable(struct pwm_device *pwm)
{
if (pwm && !test_and_set_bit(PWMF_ENABLED, &pwm->flags))
return pwm->chip->ops->enable(pwm->chip, pwm);
return pwm ? 0 : -EINVAL;
}
EXPORT_SYMBOL_GPL(pwm_enable);
/**
* pwm_disable() - stop a PWM output toggling
* @pwm: PWM device
*/
void pwm_disable(struct pwm_device *pwm)
{
if (pwm && test_and_clear_bit(PWMF_ENABLED, &pwm->flags))
pwm->chip->ops->disable(pwm->chip, pwm);
}
EXPORT_SYMBOL_GPL(pwm_disable);
static struct pwm_chip *of_node_to_pwmchip(struct device_node *np)
{
struct pwm_chip *chip;
mutex_lock(&pwm_lock);
list_for_each_entry(chip, &pwm_chips, list)
if (chip->dev && chip->dev->of_node == np) {
mutex_unlock(&pwm_lock);
return chip;
}
mutex_unlock(&pwm_lock);
return ERR_PTR(-EPROBE_DEFER);
}
/**
* of_pwm_get() - request a PWM via the PWM framework
* @np: device node to get the PWM from
* @con_id: consumer name
*
* Returns the PWM device parsed from the phandle and index specified in the
* "pwms" property of a device tree node or a negative error-code on failure.
* Values parsed from the device tree are stored in the returned PWM device
* object.
*
* If con_id is NULL, the first PWM device listed in the "pwms" property will
* be requested. Otherwise the "pwm-names" property is used to do a reverse
* lookup of the PWM index. This also means that the "pwm-names" property
* becomes mandatory for devices that look up the PWM device via the con_id
* parameter.
*/
struct pwm_device *of_pwm_get(struct device_node *np, const char *con_id)
{
struct pwm_device *pwm = NULL;
struct of_phandle_args args;
struct pwm_chip *pc;
int index = 0;
int err;
if (con_id) {
index = of_property_match_string(np, "pwm-names", con_id);
if (index < 0)
return ERR_PTR(index);
}
err = of_parse_phandle_with_args(np, "pwms", "#pwm-cells", index,
&args);
if (err) {
pr_debug("%s(): can't parse \"pwms\" property\n", __func__);
return ERR_PTR(err);
}
pc = of_node_to_pwmchip(args.np);
if (IS_ERR(pc)) {
pr_debug("%s(): PWM chip not found\n", __func__);
pwm = ERR_CAST(pc);
goto put;
}
if (args.args_count != pc->of_pwm_n_cells) {
pr_debug("%s: wrong #pwm-cells for %s\n", np->full_name,
args.np->full_name);
pwm = ERR_PTR(-EINVAL);
goto put;
}
pwm = pc->of_xlate(pc, &args);
if (IS_ERR(pwm))
goto put;
/*
* If a consumer name was not given, try to look it up from the
* "pwm-names" property if it exists. Otherwise use the name of
* the user device node.
*/
if (!con_id) {
err = of_property_read_string_index(np, "pwm-names", index,
&con_id);
if (err < 0)
con_id = np->name;
}
pwm->label = con_id;
put:
of_node_put(args.np);
return pwm;
}
EXPORT_SYMBOL_GPL(of_pwm_get);
/**
* pwm_add_table() - register PWM device consumers
* @table: array of consumers to register
* @num: number of consumers in table
*/
void __init pwm_add_table(struct pwm_lookup *table, size_t num)
{
mutex_lock(&pwm_lookup_lock);
while (num--) {
list_add_tail(&table->list, &pwm_lookup_list);
table++;
}
mutex_unlock(&pwm_lookup_lock);
}
/**
* pwm_get() - look up and request a PWM device
* @dev: device for PWM consumer
* @con_id: consumer name
*
* Lookup is first attempted using DT. If the device was not instantiated from
* a device tree, a PWM chip and a relative index is looked up via a table
* supplied by board setup code (see pwm_add_table()).
*
* Once a PWM chip has been found the specified PWM device will be requested
* and is ready to be used.
*/
struct pwm_device *pwm_get(struct device *dev, const char *con_id)
{
struct pwm_device *pwm = ERR_PTR(-EPROBE_DEFER);
const char *dev_id = dev ? dev_name(dev) : NULL;
struct pwm_chip *chip = NULL;
unsigned int index = 0;
unsigned int best = 0;
struct pwm_lookup *p;
unsigned int match;
/* look up via DT first */
if (IS_ENABLED(CONFIG_OF) && dev && dev->of_node)
return of_pwm_get(dev->of_node, con_id);
/*
* We look up the provider in the static table typically provided by
* board setup code. We first try to lookup the consumer device by
* name. If the consumer device was passed in as NULL or if no match
* was found, we try to find the consumer by directly looking it up
* by name.
*
* If a match is found, the provider PWM chip is looked up by name
* and a PWM device is requested using the PWM device per-chip index.
*
* The lookup algorithm was shamelessly taken from the clock
* framework:
*
* We do slightly fuzzy matching here:
* An entry with a NULL ID is assumed to be a wildcard.
* If an entry has a device ID, it must match
* If an entry has a connection ID, it must match
* Then we take the most specific entry - with the following order
* of precedence: dev+con > dev only > con only.
*/
mutex_lock(&pwm_lookup_lock);
list_for_each_entry(p, &pwm_lookup_list, list) {
match = 0;
if (p->dev_id) {
if (!dev_id || strcmp(p->dev_id, dev_id))
continue;
match += 2;
}
if (p->con_id) {
if (!con_id || strcmp(p->con_id, con_id))
continue;
match += 1;
}
if (match > best) {
chip = pwmchip_find_by_name(p->provider);
index = p->index;
if (match != 3)
best = match;
else
break;
}
}
if (chip)
pwm = pwm_request_from_chip(chip, index, con_id ?: dev_id);
mutex_unlock(&pwm_lookup_lock);
return pwm;
}
EXPORT_SYMBOL_GPL(pwm_get);
/**
* pwm_put() - release a PWM device
* @pwm: PWM device
*/
void pwm_put(struct pwm_device *pwm)
{
if (!pwm)
return;
mutex_lock(&pwm_lock);
if (!test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) {
pr_warn("PWM device already freed\n");
goto out;
}
if (pwm->chip->ops->free)
pwm->chip->ops->free(pwm->chip, pwm);
pwm->label = NULL;
module_put(pwm->chip->ops->owner);
out:
mutex_unlock(&pwm_lock);
}
EXPORT_SYMBOL_GPL(pwm_put);
static void devm_pwm_release(struct device *dev, void *res)
{
pwm_put(*(struct pwm_device **)res);
}
/**
* devm_pwm_get() - resource managed pwm_get()
* @dev: device for PWM consumer
* @con_id: consumer name
*
* This function performs like pwm_get() but the acquired PWM device will
* automatically be released on driver detach.
*/
struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id)
{
struct pwm_device **ptr, *pwm;
ptr = devres_alloc(devm_pwm_release, sizeof(**ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
pwm = pwm_get(dev, con_id);
if (!IS_ERR(pwm)) {
*ptr = pwm;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return pwm;
}
EXPORT_SYMBOL_GPL(devm_pwm_get);
/**
* devm_of_pwm_get() - resource managed of_pwm_get()
* @dev: device for PWM consumer
* @np: device node to get the PWM from
* @con_id: consumer name
*
* This function performs like of_pwm_get() but the acquired PWM device will
* automatically be released on driver detach.
*/
struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np,
const char *con_id)
{
struct pwm_device **ptr, *pwm;
ptr = devres_alloc(devm_pwm_release, sizeof(**ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
pwm = of_pwm_get(np, con_id);
if (!IS_ERR(pwm)) {
*ptr = pwm;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return pwm;
}
EXPORT_SYMBOL_GPL(devm_of_pwm_get);
static int devm_pwm_match(struct device *dev, void *res, void *data)
{
struct pwm_device **p = res;
if (WARN_ON(!p || !*p))
return 0;
return *p == data;
}
/**
* devm_pwm_put() - resource managed pwm_put()
* @dev: device for PWM consumer
* @pwm: PWM device
*
* Release a PWM previously allocated using devm_pwm_get(). Calling this
* function is usually not needed because devm-allocated resources are
* automatically released on driver detach.
*/
void devm_pwm_put(struct device *dev, struct pwm_device *pwm)
{
WARN_ON(devres_release(dev, devm_pwm_release, devm_pwm_match, pwm));
}
EXPORT_SYMBOL_GPL(devm_pwm_put);
/**
* pwm_can_sleep() - report whether PWM access will sleep
* @pwm: PWM device
*
* It returns true if accessing the PWM can sleep, false otherwise.
*/
bool pwm_can_sleep(struct pwm_device *pwm)
{
return pwm->chip->can_sleep;
}
EXPORT_SYMBOL_GPL(pwm_can_sleep);
#ifdef CONFIG_DEBUG_FS
static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s)
{
unsigned int i;
for (i = 0; i < chip->npwm; i++) {
struct pwm_device *pwm = &chip->pwms[i];
seq_printf(s, " pwm-%-3d (%-20.20s):", i, pwm->label);
if (test_bit(PWMF_REQUESTED, &pwm->flags))
seq_printf(s, " requested");
if (test_bit(PWMF_ENABLED, &pwm->flags))
seq_printf(s, " enabled");
seq_printf(s, "\n");
}
}
static void *pwm_seq_start(struct seq_file *s, loff_t *pos)
{
mutex_lock(&pwm_lock);
s->private = "";
return seq_list_start(&pwm_chips, *pos);
}
static void *pwm_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
s->private = "\n";
return seq_list_next(v, &pwm_chips, pos);
}
static void pwm_seq_stop(struct seq_file *s, void *v)
{
mutex_unlock(&pwm_lock);
}
static int pwm_seq_show(struct seq_file *s, void *v)
{
struct pwm_chip *chip = list_entry(v, struct pwm_chip, list);
seq_printf(s, "%s%s/%s, %d PWM device%s\n", (char *)s->private,
chip->dev->bus ? chip->dev->bus->name : "no-bus",
dev_name(chip->dev), chip->npwm,
(chip->npwm != 1) ? "s" : "");
if (chip->ops->dbg_show)
chip->ops->dbg_show(chip, s);
else
pwm_dbg_show(chip, s);
return 0;
}
static const struct seq_operations pwm_seq_ops = {
.start = pwm_seq_start,
.next = pwm_seq_next,
.stop = pwm_seq_stop,
.show = pwm_seq_show,
};
static int pwm_seq_open(struct inode *inode, struct file *file)
{
return seq_open(file, &pwm_seq_ops);
}
static const struct file_operations pwm_debugfs_ops = {
.owner = THIS_MODULE,
.open = pwm_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
static int __init pwm_debugfs_init(void)
{
debugfs_create_file("pwm", S_IFREG | S_IRUGO, NULL, NULL,
&pwm_debugfs_ops);
return 0;
}
subsys_initcall(pwm_debugfs_init);
#endif /* CONFIG_DEBUG_FS */

151
drivers/pwm/pwm-ab8500.c Normal file
View File

@@ -0,0 +1,151 @@
/*
* Copyright (C) ST-Ericsson SA 2010
*
* Author: Arun R Murthy <arun.murthy@stericsson.com>
* License terms: GNU General Public License (GPL) version 2
*/
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/pwm.h>
#include <linux/mfd/abx500.h>
#include <linux/mfd/abx500/ab8500.h>
#include <linux/module.h>
/*
* PWM Out generators
* Bank: 0x10
*/
#define AB8500_PWM_OUT_CTRL1_REG 0x60
#define AB8500_PWM_OUT_CTRL2_REG 0x61
#define AB8500_PWM_OUT_CTRL7_REG 0x66
/* backlight driver constants */
#define ENABLE_PWM 1
#define DISABLE_PWM 0
struct ab8500_pwm_chip {
struct pwm_chip chip;
};
static int ab8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
int ret = 0;
unsigned int higher_val, lower_val;
u8 reg;
/*
* get the first 8 bits that are be written to
* AB8500_PWM_OUT_CTRL1_REG[0:7]
*/
lower_val = duty_ns & 0x00FF;
/*
* get bits [9:10] that are to be written to
* AB8500_PWM_OUT_CTRL2_REG[0:1]
*/
higher_val = ((duty_ns & 0x0300) >> 8);
reg = AB8500_PWM_OUT_CTRL1_REG + ((chip->base - 1) * 2);
ret = abx500_set_register_interruptible(chip->dev, AB8500_MISC,
reg, (u8)lower_val);
if (ret < 0)
return ret;
ret = abx500_set_register_interruptible(chip->dev, AB8500_MISC,
(reg + 1), (u8)higher_val);
return ret;
}
static int ab8500_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
int ret;
ret = abx500_mask_and_set_register_interruptible(chip->dev,
AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG,
1 << (chip->base - 1), ENABLE_PWM);
if (ret < 0)
dev_err(chip->dev, "%s: Failed to enable PWM, Error %d\n",
pwm->label, ret);
return ret;
}
static void ab8500_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
int ret;
ret = abx500_mask_and_set_register_interruptible(chip->dev,
AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG,
1 << (chip->base - 1), DISABLE_PWM);
if (ret < 0)
dev_err(chip->dev, "%s: Failed to disable PWM, Error %d\n",
pwm->label, ret);
return;
}
static const struct pwm_ops ab8500_pwm_ops = {
.config = ab8500_pwm_config,
.enable = ab8500_pwm_enable,
.disable = ab8500_pwm_disable,
.owner = THIS_MODULE,
};
static int ab8500_pwm_probe(struct platform_device *pdev)
{
struct ab8500_pwm_chip *ab8500;
int err;
/*
* Nothing to be done in probe, this is required to get the
* device which is required for ab8500 read and write
*/
ab8500 = devm_kzalloc(&pdev->dev, sizeof(*ab8500), GFP_KERNEL);
if (ab8500 == NULL) {
dev_err(&pdev->dev, "failed to allocate memory\n");
return -ENOMEM;
}
ab8500->chip.dev = &pdev->dev;
ab8500->chip.ops = &ab8500_pwm_ops;
ab8500->chip.base = pdev->id;
ab8500->chip.npwm = 1;
err = pwmchip_add(&ab8500->chip);
if (err < 0)
return err;
dev_dbg(&pdev->dev, "pwm probe successful\n");
platform_set_drvdata(pdev, ab8500);
return 0;
}
static int ab8500_pwm_remove(struct platform_device *pdev)
{
struct ab8500_pwm_chip *ab8500 = platform_get_drvdata(pdev);
int err;
err = pwmchip_remove(&ab8500->chip);
if (err < 0)
return err;
dev_dbg(&pdev->dev, "pwm driver removed\n");
return 0;
}
static struct platform_driver ab8500_pwm_driver = {
.driver = {
.name = "ab8500-pwm",
.owner = THIS_MODULE,
},
.probe = ab8500_pwm_probe,
.remove = ab8500_pwm_remove,
};
module_platform_driver(ab8500_pwm_driver);
MODULE_AUTHOR("Arun MURTHY <arun.murthy@stericsson.com>");
MODULE_DESCRIPTION("AB8500 Pulse Width Modulation Driver");
MODULE_ALIAS("platform:ab8500-pwm");
MODULE_LICENSE("GPL v2");

446
drivers/pwm/pwm-atmel-tcb.c Normal file
View File

@@ -0,0 +1,446 @@
/*
* Copyright (C) Overkiz SAS 2012
*
* Author: Boris BREZILLON <b.brezillon@overkiz.com>
* License terms: GNU General Public License (GPL) version 2
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/clocksource.h>
#include <linux/clockchips.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/atmel_tc.h>
#include <linux/pwm.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#define NPWM 6
#define ATMEL_TC_ACMR_MASK (ATMEL_TC_ACPA | ATMEL_TC_ACPC | \
ATMEL_TC_AEEVT | ATMEL_TC_ASWTRG)
#define ATMEL_TC_BCMR_MASK (ATMEL_TC_BCPB | ATMEL_TC_BCPC | \
ATMEL_TC_BEEVT | ATMEL_TC_BSWTRG)
struct atmel_tcb_pwm_device {
enum pwm_polarity polarity; /* PWM polarity */
unsigned div; /* PWM clock divider */
unsigned duty; /* PWM duty expressed in clk cycles */
unsigned period; /* PWM period expressed in clk cycles */
};
struct atmel_tcb_pwm_chip {
struct pwm_chip chip;
spinlock_t lock;
struct atmel_tc *tc;
struct atmel_tcb_pwm_device *pwms[NPWM];
};
static inline struct atmel_tcb_pwm_chip *to_tcb_chip(struct pwm_chip *chip)
{
return container_of(chip, struct atmel_tcb_pwm_chip, chip);
}
static int atmel_tcb_pwm_set_polarity(struct pwm_chip *chip,
struct pwm_device *pwm,
enum pwm_polarity polarity)
{
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
tcbpwm->polarity = polarity;
return 0;
}
static int atmel_tcb_pwm_request(struct pwm_chip *chip,
struct pwm_device *pwm)
{
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
struct atmel_tcb_pwm_device *tcbpwm;
struct atmel_tc *tc = tcbpwmc->tc;
void __iomem *regs = tc->regs;
unsigned group = pwm->hwpwm / 2;
unsigned index = pwm->hwpwm % 2;
unsigned cmr;
int ret;
tcbpwm = devm_kzalloc(chip->dev, sizeof(*tcbpwm), GFP_KERNEL);
if (!tcbpwm)
return -ENOMEM;
ret = clk_enable(tc->clk[group]);
if (ret) {
devm_kfree(chip->dev, tcbpwm);
return ret;
}
pwm_set_chip_data(pwm, tcbpwm);
tcbpwm->polarity = PWM_POLARITY_NORMAL;
tcbpwm->duty = 0;
tcbpwm->period = 0;
tcbpwm->div = 0;
spin_lock(&tcbpwmc->lock);
cmr = __raw_readl(regs + ATMEL_TC_REG(group, CMR));
/*
* Get init config from Timer Counter registers if
* Timer Counter is already configured as a PWM generator.
*/
if (cmr & ATMEL_TC_WAVE) {
if (index == 0)
tcbpwm->duty =
__raw_readl(regs + ATMEL_TC_REG(group, RA));
else
tcbpwm->duty =
__raw_readl(regs + ATMEL_TC_REG(group, RB));
tcbpwm->div = cmr & ATMEL_TC_TCCLKS;
tcbpwm->period = __raw_readl(regs + ATMEL_TC_REG(group, RC));
cmr &= (ATMEL_TC_TCCLKS | ATMEL_TC_ACMR_MASK |
ATMEL_TC_BCMR_MASK);
} else
cmr = 0;
cmr |= ATMEL_TC_WAVE | ATMEL_TC_WAVESEL_UP_AUTO | ATMEL_TC_EEVT_XC0;
__raw_writel(cmr, regs + ATMEL_TC_REG(group, CMR));
spin_unlock(&tcbpwmc->lock);
tcbpwmc->pwms[pwm->hwpwm] = tcbpwm;
return 0;
}
static void atmel_tcb_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
struct atmel_tc *tc = tcbpwmc->tc;
clk_disable(tc->clk[pwm->hwpwm / 2]);
tcbpwmc->pwms[pwm->hwpwm] = NULL;
devm_kfree(chip->dev, tcbpwm);
}
static void atmel_tcb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
struct atmel_tc *tc = tcbpwmc->tc;
void __iomem *regs = tc->regs;
unsigned group = pwm->hwpwm / 2;
unsigned index = pwm->hwpwm % 2;
unsigned cmr;
enum pwm_polarity polarity = tcbpwm->polarity;
/*
* If duty is 0 the timer will be stopped and we have to
* configure the output correctly on software trigger:
* - set output to high if PWM_POLARITY_INVERSED
* - set output to low if PWM_POLARITY_NORMAL
*
* This is why we're reverting polarity in this case.
*/
if (tcbpwm->duty == 0)
polarity = !polarity;
spin_lock(&tcbpwmc->lock);
cmr = __raw_readl(regs + ATMEL_TC_REG(group, CMR));
/* flush old setting and set the new one */
if (index == 0) {
cmr &= ~ATMEL_TC_ACMR_MASK;
if (polarity == PWM_POLARITY_INVERSED)
cmr |= ATMEL_TC_ASWTRG_CLEAR;
else
cmr |= ATMEL_TC_ASWTRG_SET;
} else {
cmr &= ~ATMEL_TC_BCMR_MASK;
if (polarity == PWM_POLARITY_INVERSED)
cmr |= ATMEL_TC_BSWTRG_CLEAR;
else
cmr |= ATMEL_TC_BSWTRG_SET;
}
__raw_writel(cmr, regs + ATMEL_TC_REG(group, CMR));
/*
* Use software trigger to apply the new setting.
* If both PWM devices in this group are disabled we stop the clock.
*/
if (!(cmr & (ATMEL_TC_ACPC | ATMEL_TC_BCPC)))
__raw_writel(ATMEL_TC_SWTRG | ATMEL_TC_CLKDIS,
regs + ATMEL_TC_REG(group, CCR));
else
__raw_writel(ATMEL_TC_SWTRG, regs +
ATMEL_TC_REG(group, CCR));
spin_unlock(&tcbpwmc->lock);
}
static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
struct atmel_tc *tc = tcbpwmc->tc;
void __iomem *regs = tc->regs;
unsigned group = pwm->hwpwm / 2;
unsigned index = pwm->hwpwm % 2;
u32 cmr;
enum pwm_polarity polarity = tcbpwm->polarity;
/*
* If duty is 0 the timer will be stopped and we have to
* configure the output correctly on software trigger:
* - set output to high if PWM_POLARITY_INVERSED
* - set output to low if PWM_POLARITY_NORMAL
*
* This is why we're reverting polarity in this case.
*/
if (tcbpwm->duty == 0)
polarity = !polarity;
spin_lock(&tcbpwmc->lock);
cmr = __raw_readl(regs + ATMEL_TC_REG(group, CMR));
/* flush old setting and set the new one */
cmr &= ~ATMEL_TC_TCCLKS;
if (index == 0) {
cmr &= ~ATMEL_TC_ACMR_MASK;
/* Set CMR flags according to given polarity */
if (polarity == PWM_POLARITY_INVERSED)
cmr |= ATMEL_TC_ASWTRG_CLEAR;
else
cmr |= ATMEL_TC_ASWTRG_SET;
} else {
cmr &= ~ATMEL_TC_BCMR_MASK;
if (polarity == PWM_POLARITY_INVERSED)
cmr |= ATMEL_TC_BSWTRG_CLEAR;
else
cmr |= ATMEL_TC_BSWTRG_SET;
}
/*
* If duty is 0 or equal to period there's no need to register
* a specific action on RA/RB and RC compare.
* The output will be configured on software trigger and keep
* this config till next config call.
*/
if (tcbpwm->duty != tcbpwm->period && tcbpwm->duty > 0) {
if (index == 0) {
if (polarity == PWM_POLARITY_INVERSED)
cmr |= ATMEL_TC_ACPA_SET | ATMEL_TC_ACPC_CLEAR;
else
cmr |= ATMEL_TC_ACPA_CLEAR | ATMEL_TC_ACPC_SET;
} else {
if (polarity == PWM_POLARITY_INVERSED)
cmr |= ATMEL_TC_BCPB_SET | ATMEL_TC_BCPC_CLEAR;
else
cmr |= ATMEL_TC_BCPB_CLEAR | ATMEL_TC_BCPC_SET;
}
}
__raw_writel(cmr, regs + ATMEL_TC_REG(group, CMR));
if (index == 0)
__raw_writel(tcbpwm->duty, regs + ATMEL_TC_REG(group, RA));
else
__raw_writel(tcbpwm->duty, regs + ATMEL_TC_REG(group, RB));
__raw_writel(tcbpwm->period, regs + ATMEL_TC_REG(group, RC));
/* Use software trigger to apply the new setting */
__raw_writel(ATMEL_TC_CLKEN | ATMEL_TC_SWTRG,
regs + ATMEL_TC_REG(group, CCR));
spin_unlock(&tcbpwmc->lock);
return 0;
}
static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
unsigned group = pwm->hwpwm / 2;
unsigned index = pwm->hwpwm % 2;
struct atmel_tcb_pwm_device *atcbpwm = NULL;
struct atmel_tc *tc = tcbpwmc->tc;
int i;
int slowclk = 0;
unsigned period;
unsigned duty;
unsigned rate = clk_get_rate(tc->clk[group]);
unsigned long long min;
unsigned long long max;
/*
* Find best clk divisor:
* the smallest divisor which can fulfill the period_ns requirements.
*/
for (i = 0; i < 5; ++i) {
if (atmel_tc_divisors[i] == 0) {
slowclk = i;
continue;
}
min = div_u64((u64)NSEC_PER_SEC * atmel_tc_divisors[i], rate);
max = min << tc->tcb_config->counter_width;
if (max >= period_ns)
break;
}
/*
* If none of the divisor are small enough to represent period_ns
* take slow clock (32KHz).
*/
if (i == 5) {
i = slowclk;
rate = 32768;
min = div_u64(NSEC_PER_SEC, rate);
max = min << 16;
/* If period is too big return ERANGE error */
if (max < period_ns)
return -ERANGE;
}
duty = div_u64(duty_ns, min);
period = div_u64(period_ns, min);
if (index == 0)
atcbpwm = tcbpwmc->pwms[pwm->hwpwm + 1];
else
atcbpwm = tcbpwmc->pwms[pwm->hwpwm - 1];
/*
* PWM devices provided by TCB driver are grouped by 2:
* - group 0: PWM 0 & 1
* - group 1: PWM 2 & 3
* - group 2: PWM 4 & 5
*
* PWM devices in a given group must be configured with the
* same period_ns.
*
* We're checking the period value of the second PWM device
* in this group before applying the new config.
*/
if ((atcbpwm && atcbpwm->duty > 0 &&
atcbpwm->duty != atcbpwm->period) &&
(atcbpwm->div != i || atcbpwm->period != period)) {
dev_err(chip->dev,
"failed to configure period_ns: PWM group already configured with a different value\n");
return -EINVAL;
}
tcbpwm->period = period;
tcbpwm->div = i;
tcbpwm->duty = duty;
/* If the PWM is enabled, call enable to apply the new conf */
if (test_bit(PWMF_ENABLED, &pwm->flags))
atmel_tcb_pwm_enable(chip, pwm);
return 0;
}
static const struct pwm_ops atmel_tcb_pwm_ops = {
.request = atmel_tcb_pwm_request,
.free = atmel_tcb_pwm_free,
.config = atmel_tcb_pwm_config,
.set_polarity = atmel_tcb_pwm_set_polarity,
.enable = atmel_tcb_pwm_enable,
.disable = atmel_tcb_pwm_disable,
.owner = THIS_MODULE,
};
static int atmel_tcb_pwm_probe(struct platform_device *pdev)
{
struct atmel_tcb_pwm_chip *tcbpwm;
struct device_node *np = pdev->dev.of_node;
struct atmel_tc *tc;
int err;
int tcblock;
err = of_property_read_u32(np, "tc-block", &tcblock);
if (err < 0) {
dev_err(&pdev->dev,
"failed to get Timer Counter Block number from device tree (error: %d)\n",
err);
return err;
}
tc = atmel_tc_alloc(tcblock, "tcb-pwm");
if (tc == NULL) {
dev_err(&pdev->dev, "failed to allocate Timer Counter Block\n");
return -ENOMEM;
}
tcbpwm = devm_kzalloc(&pdev->dev, sizeof(*tcbpwm), GFP_KERNEL);
if (tcbpwm == NULL) {
atmel_tc_free(tc);
dev_err(&pdev->dev, "failed to allocate memory\n");
return -ENOMEM;
}
tcbpwm->chip.dev = &pdev->dev;
tcbpwm->chip.ops = &atmel_tcb_pwm_ops;
tcbpwm->chip.of_xlate = of_pwm_xlate_with_flags;
tcbpwm->chip.of_pwm_n_cells = 3;
tcbpwm->chip.base = -1;
tcbpwm->chip.npwm = NPWM;
tcbpwm->tc = tc;
spin_lock_init(&tcbpwm->lock);
err = pwmchip_add(&tcbpwm->chip);
if (err < 0) {
atmel_tc_free(tc);
return err;
}
platform_set_drvdata(pdev, tcbpwm);
return 0;
}
static int atmel_tcb_pwm_remove(struct platform_device *pdev)
{
struct atmel_tcb_pwm_chip *tcbpwm = platform_get_drvdata(pdev);
int err;
err = pwmchip_remove(&tcbpwm->chip);
if (err < 0)
return err;
atmel_tc_free(tcbpwm->tc);
return 0;
}
static const struct of_device_id atmel_tcb_pwm_dt_ids[] = {
{ .compatible = "atmel,tcb-pwm", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, atmel_tcb_pwm_dt_ids);
static struct platform_driver atmel_tcb_pwm_driver = {
.driver = {
.name = "atmel-tcb-pwm",
.of_match_table = atmel_tcb_pwm_dt_ids,
},
.probe = atmel_tcb_pwm_probe,
.remove = atmel_tcb_pwm_remove,
};
module_platform_driver(atmel_tcb_pwm_driver);
MODULE_AUTHOR("Boris BREZILLON <b.brezillon@overkiz.com>");
MODULE_DESCRIPTION("Atmel Timer Counter Pulse Width Modulation Driver");
MODULE_LICENSE("GPL v2");

159
drivers/pwm/pwm-bfin.c Normal file
View File

@@ -0,0 +1,159 @@
/*
* Blackfin Pulse Width Modulation (PWM) core
*
* Copyright (c) 2011 Analog Devices Inc.
*
* Licensed under the GPL-2 or later.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <asm/gptimers.h>
#include <asm/portmux.h>
struct bfin_pwm_chip {
struct pwm_chip chip;
};
struct bfin_pwm {
unsigned short pin;
};
static const unsigned short pwm_to_gptimer_per[] = {
P_TMR0, P_TMR1, P_TMR2, P_TMR3, P_TMR4, P_TMR5,
P_TMR6, P_TMR7, P_TMR8, P_TMR9, P_TMR10, P_TMR11,
};
static int bfin_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct bfin_pwm *priv;
int ret;
if (pwm->hwpwm >= ARRAY_SIZE(pwm_to_gptimer_per))
return -EINVAL;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->pin = pwm_to_gptimer_per[pwm->hwpwm];
ret = peripheral_request(priv->pin, NULL);
if (ret) {
kfree(priv);
return ret;
}
pwm_set_chip_data(pwm, priv);
return 0;
}
static void bfin_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct bfin_pwm *priv = pwm_get_chip_data(pwm);
if (priv) {
peripheral_free(priv->pin);
kfree(priv);
}
}
static int bfin_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct bfin_pwm *priv = pwm_get_chip_data(pwm);
unsigned long period, duty;
unsigned long long val;
val = (unsigned long long)get_sclk() * period_ns;
do_div(val, NSEC_PER_SEC);
period = val;
val = (unsigned long long)period * duty_ns;
do_div(val, period_ns);
duty = period - val;
if (duty >= period)
duty = period - 1;
set_gptimer_config(priv->pin, TIMER_MODE_PWM | TIMER_PERIOD_CNT);
set_gptimer_pwidth(priv->pin, duty);
set_gptimer_period(priv->pin, period);
return 0;
}
static int bfin_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct bfin_pwm *priv = pwm_get_chip_data(pwm);
enable_gptimer(priv->pin);
return 0;
}
static void bfin_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct bfin_pwm *priv = pwm_get_chip_data(pwm);
disable_gptimer(priv->pin);
}
static struct pwm_ops bfin_pwm_ops = {
.request = bfin_pwm_request,
.free = bfin_pwm_free,
.config = bfin_pwm_config,
.enable = bfin_pwm_enable,
.disable = bfin_pwm_disable,
.owner = THIS_MODULE,
};
static int bfin_pwm_probe(struct platform_device *pdev)
{
struct bfin_pwm_chip *pwm;
int ret;
pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
if (!pwm) {
dev_err(&pdev->dev, "failed to allocate memory\n");
return -ENOMEM;
}
platform_set_drvdata(pdev, pwm);
pwm->chip.dev = &pdev->dev;
pwm->chip.ops = &bfin_pwm_ops;
pwm->chip.base = -1;
pwm->chip.npwm = 12;
ret = pwmchip_add(&pwm->chip);
if (ret < 0) {
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
return ret;
}
return 0;
}
static int bfin_pwm_remove(struct platform_device *pdev)
{
struct bfin_pwm_chip *pwm = platform_get_drvdata(pdev);
return pwmchip_remove(&pwm->chip);
}
static struct platform_driver bfin_pwm_driver = {
.driver = {
.name = "bfin-pwm",
},
.probe = bfin_pwm_probe,
.remove = bfin_pwm_remove,
};
module_platform_driver(bfin_pwm_driver);
MODULE_LICENSE("GPL");

307
drivers/pwm/pwm-imx.c Normal file
View File

@@ -0,0 +1,307 @@
/*
* simple driver for PWM (Pulse Width Modulator) controller
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* Derived from pxa PWM driver by eric miao <eric.miao@marvell.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/pwm.h>
#include <linux/of_device.h>
/* i.MX1 and i.MX21 share the same PWM function block: */
#define MX1_PWMC 0x00 /* PWM Control Register */
#define MX1_PWMS 0x04 /* PWM Sample Register */
#define MX1_PWMP 0x08 /* PWM Period Register */
#define MX1_PWMC_EN (1 << 4)
/* i.MX27, i.MX31, i.MX35 share the same PWM function block: */
#define MX3_PWMCR 0x00 /* PWM Control Register */
#define MX3_PWMSAR 0x0C /* PWM Sample Register */
#define MX3_PWMPR 0x10 /* PWM Period Register */
#define MX3_PWMCR_PRESCALER(x) (((x - 1) & 0xFFF) << 4)
#define MX3_PWMCR_DOZEEN (1 << 24)
#define MX3_PWMCR_WAITEN (1 << 23)
#define MX3_PWMCR_DBGEN (1 << 22)
#define MX3_PWMCR_CLKSRC_IPG_HIGH (2 << 16)
#define MX3_PWMCR_CLKSRC_IPG (1 << 16)
#define MX3_PWMCR_EN (1 << 0)
struct imx_chip {
struct clk *clk_per;
struct clk *clk_ipg;
void __iomem *mmio_base;
struct pwm_chip chip;
int (*config)(struct pwm_chip *chip,
struct pwm_device *pwm, int duty_ns, int period_ns);
void (*set_enable)(struct pwm_chip *chip, bool enable);
};
#define to_imx_chip(chip) container_of(chip, struct imx_chip, chip)
static int imx_pwm_config_v1(struct pwm_chip *chip,
struct pwm_device *pwm, int duty_ns, int period_ns)
{
struct imx_chip *imx = to_imx_chip(chip);
/*
* The PWM subsystem allows for exact frequencies. However,
* I cannot connect a scope on my device to the PWM line and
* thus cannot provide the program the PWM controller
* exactly. Instead, I'm relying on the fact that the
* Bootloader (u-boot or WinCE+haret) has programmed the PWM
* function group already. So I'll just modify the PWM sample
* register to follow the ratio of duty_ns vs. period_ns
* accordingly.
*
* This is good enough for programming the brightness of
* the LCD backlight.
*
* The real implementation would divide PERCLK[0] first by
* both the prescaler (/1 .. /128) and then by CLKSEL
* (/2 .. /16).
*/
u32 max = readl(imx->mmio_base + MX1_PWMP);
u32 p = max * duty_ns / period_ns;
writel(max - p, imx->mmio_base + MX1_PWMS);
return 0;
}
static void imx_pwm_set_enable_v1(struct pwm_chip *chip, bool enable)
{
struct imx_chip *imx = to_imx_chip(chip);
u32 val;
val = readl(imx->mmio_base + MX1_PWMC);
if (enable)
val |= MX1_PWMC_EN;
else
val &= ~MX1_PWMC_EN;
writel(val, imx->mmio_base + MX1_PWMC);
}
static int imx_pwm_config_v2(struct pwm_chip *chip,
struct pwm_device *pwm, int duty_ns, int period_ns)
{
struct imx_chip *imx = to_imx_chip(chip);
unsigned long long c;
unsigned long period_cycles, duty_cycles, prescale;
u32 cr;
c = clk_get_rate(imx->clk_per);
c = c * period_ns;
do_div(c, 1000000000);
period_cycles = c;
prescale = period_cycles / 0x10000 + 1;
period_cycles /= prescale;
c = (unsigned long long)period_cycles * duty_ns;
do_div(c, period_ns);
duty_cycles = c;
/*
* according to imx pwm RM, the real period value should be
* PERIOD value in PWMPR plus 2.
*/
if (period_cycles > 2)
period_cycles -= 2;
else
period_cycles = 0;
writel(duty_cycles, imx->mmio_base + MX3_PWMSAR);
writel(period_cycles, imx->mmio_base + MX3_PWMPR);
cr = MX3_PWMCR_PRESCALER(prescale) |
MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN |
MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH;
if (test_bit(PWMF_ENABLED, &pwm->flags))
cr |= MX3_PWMCR_EN;
writel(cr, imx->mmio_base + MX3_PWMCR);
return 0;
}
static void imx_pwm_set_enable_v2(struct pwm_chip *chip, bool enable)
{
struct imx_chip *imx = to_imx_chip(chip);
u32 val;
val = readl(imx->mmio_base + MX3_PWMCR);
if (enable)
val |= MX3_PWMCR_EN;
else
val &= ~MX3_PWMCR_EN;
writel(val, imx->mmio_base + MX3_PWMCR);
}
static int imx_pwm_config(struct pwm_chip *chip,
struct pwm_device *pwm, int duty_ns, int period_ns)
{
struct imx_chip *imx = to_imx_chip(chip);
int ret;
ret = clk_prepare_enable(imx->clk_ipg);
if (ret)
return ret;
ret = imx->config(chip, pwm, duty_ns, period_ns);
clk_disable_unprepare(imx->clk_ipg);
return ret;
}
static int imx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct imx_chip *imx = to_imx_chip(chip);
int ret;
ret = clk_prepare_enable(imx->clk_per);
if (ret)
return ret;
imx->set_enable(chip, true);
return 0;
}
static void imx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct imx_chip *imx = to_imx_chip(chip);
imx->set_enable(chip, false);
clk_disable_unprepare(imx->clk_per);
}
static struct pwm_ops imx_pwm_ops = {
.enable = imx_pwm_enable,
.disable = imx_pwm_disable,
.config = imx_pwm_config,
.owner = THIS_MODULE,
};
struct imx_pwm_data {
int (*config)(struct pwm_chip *chip,
struct pwm_device *pwm, int duty_ns, int period_ns);
void (*set_enable)(struct pwm_chip *chip, bool enable);
};
static struct imx_pwm_data imx_pwm_data_v1 = {
.config = imx_pwm_config_v1,
.set_enable = imx_pwm_set_enable_v1,
};
static struct imx_pwm_data imx_pwm_data_v2 = {
.config = imx_pwm_config_v2,
.set_enable = imx_pwm_set_enable_v2,
};
static const struct of_device_id imx_pwm_dt_ids[] = {
{ .compatible = "fsl,imx1-pwm", .data = &imx_pwm_data_v1, },
{ .compatible = "fsl,imx27-pwm", .data = &imx_pwm_data_v2, },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_pwm_dt_ids);
static int imx_pwm_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id =
of_match_device(imx_pwm_dt_ids, &pdev->dev);
const struct imx_pwm_data *data;
struct imx_chip *imx;
struct resource *r;
int ret = 0;
if (!of_id)
return -ENODEV;
imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL);
if (imx == NULL) {
dev_err(&pdev->dev, "failed to allocate memory\n");
return -ENOMEM;
}
imx->clk_per = devm_clk_get(&pdev->dev, "per");
if (IS_ERR(imx->clk_per)) {
dev_err(&pdev->dev, "getting per clock failed with %ld\n",
PTR_ERR(imx->clk_per));
return PTR_ERR(imx->clk_per);
}
imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
if (IS_ERR(imx->clk_ipg)) {
dev_err(&pdev->dev, "getting ipg clock failed with %ld\n",
PTR_ERR(imx->clk_ipg));
return PTR_ERR(imx->clk_ipg);
}
imx->chip.ops = &imx_pwm_ops;
imx->chip.dev = &pdev->dev;
imx->chip.base = -1;
imx->chip.npwm = 1;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
imx->mmio_base = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(imx->mmio_base))
return PTR_ERR(imx->mmio_base);
data = of_id->data;
imx->config = data->config;
imx->set_enable = data->set_enable;
ret = pwmchip_add(&imx->chip);
if (ret < 0)
return ret;
platform_set_drvdata(pdev, imx);
return 0;
}
static int imx_pwm_remove(struct platform_device *pdev)
{
struct imx_chip *imx;
imx = platform_get_drvdata(pdev);
if (imx == NULL)
return -ENODEV;
return pwmchip_remove(&imx->chip);
}
static struct platform_driver imx_pwm_driver = {
.driver = {
.name = "imx-pwm",
.of_match_table = of_match_ptr(imx_pwm_dt_ids),
},
.probe = imx_pwm_probe,
.remove = imx_pwm_remove,
};
module_platform_driver(imx_pwm_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");

221
drivers/pwm/pwm-jz4740.c Normal file
View File

@@ -0,0 +1,221 @@
/*
* Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
* JZ4740 platform PWM support
*
* 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 of the License, or (at your
* option) any later version.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <asm/mach-jz4740/gpio.h>
#include <asm/mach-jz4740/timer.h>
#define NUM_PWM 8
static const unsigned int jz4740_pwm_gpio_list[NUM_PWM] = {
JZ_GPIO_PWM0,
JZ_GPIO_PWM1,
JZ_GPIO_PWM2,
JZ_GPIO_PWM3,
JZ_GPIO_PWM4,
JZ_GPIO_PWM5,
JZ_GPIO_PWM6,
JZ_GPIO_PWM7,
};
struct jz4740_pwm_chip {
struct pwm_chip chip;
struct clk *clk;
};
static inline struct jz4740_pwm_chip *to_jz4740(struct pwm_chip *chip)
{
return container_of(chip, struct jz4740_pwm_chip, chip);
}
static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
unsigned int gpio = jz4740_pwm_gpio_list[pwm->hwpwm];
int ret;
/*
* Timers 0 and 1 are used for system tasks, so they are unavailable
* for use as PWMs.
*/
if (pwm->hwpwm < 2)
return -EBUSY;
ret = gpio_request(gpio, pwm->label);
if (ret) {
dev_err(chip->dev, "Failed to request GPIO#%u for PWM: %d\n",
gpio, ret);
return ret;
}
jz_gpio_set_function(gpio, JZ_GPIO_FUNC_PWM);
jz4740_timer_start(pwm->hwpwm);
return 0;
}
static void jz4740_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
unsigned int gpio = jz4740_pwm_gpio_list[pwm->hwpwm];
jz4740_timer_set_ctrl(pwm->hwpwm, 0);
jz_gpio_set_function(gpio, JZ_GPIO_FUNC_NONE);
gpio_free(gpio);
jz4740_timer_stop(pwm->hwpwm);
}
static int jz4740_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
uint32_t ctrl = jz4740_timer_get_ctrl(pwm->pwm);
ctrl |= JZ_TIMER_CTRL_PWM_ENABLE;
jz4740_timer_set_ctrl(pwm->hwpwm, ctrl);
jz4740_timer_enable(pwm->hwpwm);
return 0;
}
static void jz4740_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
uint32_t ctrl = jz4740_timer_get_ctrl(pwm->hwpwm);
ctrl &= ~JZ_TIMER_CTRL_PWM_ENABLE;
jz4740_timer_disable(pwm->hwpwm);
jz4740_timer_set_ctrl(pwm->hwpwm, ctrl);
}
static int jz4740_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip);
unsigned long long tmp;
unsigned long period, duty;
unsigned int prescaler = 0;
uint16_t ctrl;
bool is_enabled;
tmp = (unsigned long long)clk_get_rate(jz4740->clk) * period_ns;
do_div(tmp, 1000000000);
period = tmp;
while (period > 0xffff && prescaler < 6) {
period >>= 2;
++prescaler;
}
if (prescaler == 6)
return -EINVAL;
tmp = (unsigned long long)period * duty_ns;
do_div(tmp, period_ns);
duty = period - tmp;
if (duty >= period)
duty = period - 1;
is_enabled = jz4740_timer_is_enabled(pwm->hwpwm);
if (is_enabled)
jz4740_pwm_disable(chip, pwm);
jz4740_timer_set_count(pwm->hwpwm, 0);
jz4740_timer_set_duty(pwm->hwpwm, duty);
jz4740_timer_set_period(pwm->hwpwm, period);
ctrl = JZ_TIMER_CTRL_PRESCALER(prescaler) | JZ_TIMER_CTRL_SRC_EXT |
JZ_TIMER_CTRL_PWM_ABBRUPT_SHUTDOWN;
jz4740_timer_set_ctrl(pwm->hwpwm, ctrl);
if (is_enabled)
jz4740_pwm_enable(chip, pwm);
return 0;
}
static const struct pwm_ops jz4740_pwm_ops = {
.request = jz4740_pwm_request,
.free = jz4740_pwm_free,
.config = jz4740_pwm_config,
.enable = jz4740_pwm_enable,
.disable = jz4740_pwm_disable,
.owner = THIS_MODULE,
};
static int jz4740_pwm_probe(struct platform_device *pdev)
{
struct jz4740_pwm_chip *jz4740;
int ret;
jz4740 = devm_kzalloc(&pdev->dev, sizeof(*jz4740), GFP_KERNEL);
if (!jz4740)
return -ENOMEM;
jz4740->clk = clk_get(NULL, "ext");
if (IS_ERR(jz4740->clk))
return PTR_ERR(jz4740->clk);
jz4740->chip.dev = &pdev->dev;
jz4740->chip.ops = &jz4740_pwm_ops;
jz4740->chip.npwm = NUM_PWM;
jz4740->chip.base = -1;
ret = pwmchip_add(&jz4740->chip);
if (ret < 0) {
clk_put(jz4740->clk);
return ret;
}
platform_set_drvdata(pdev, jz4740);
return 0;
}
static int jz4740_pwm_remove(struct platform_device *pdev)
{
struct jz4740_pwm_chip *jz4740 = platform_get_drvdata(pdev);
int ret;
ret = pwmchip_remove(&jz4740->chip);
if (ret < 0)
return ret;
clk_put(jz4740->clk);
return 0;
}
static struct platform_driver jz4740_pwm_driver = {
.driver = {
.name = "jz4740-pwm",
.owner = THIS_MODULE,
},
.probe = jz4740_pwm_probe,
.remove = jz4740_pwm_remove,
};
module_platform_driver(jz4740_pwm_driver);
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_DESCRIPTION("Ingenic JZ4740 PWM driver");
MODULE_ALIAS("platform:jz4740-pwm");
MODULE_LICENSE("GPL");

184
drivers/pwm/pwm-lpc32xx.c Normal file
View File

@@ -0,0 +1,184 @@
/*
* Copyright 2012 Alexandre Pereira da Silva <aletes.xgr@gmail.com>
*
* 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; version 2.
*
*/
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>
struct lpc32xx_pwm_chip {
struct pwm_chip chip;
struct clk *clk;
void __iomem *base;
};
#define PWM_ENABLE (1 << 31)
#define PWM_RELOADV(x) (((x) & 0xFF) << 8)
#define PWM_DUTY(x) ((x) & 0xFF)
#define to_lpc32xx_pwm_chip(_chip) \
container_of(_chip, struct lpc32xx_pwm_chip, chip)
static int lpc32xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct lpc32xx_pwm_chip *lpc32xx = to_lpc32xx_pwm_chip(chip);
unsigned long long c;
int period_cycles, duty_cycles;
u32 val;
c = clk_get_rate(lpc32xx->clk) / 256;
c = c * period_ns;
do_div(c, NSEC_PER_SEC);
/* Handle high and low extremes */
if (c == 0)
c = 1;
if (c > 255)
c = 0; /* 0 set division by 256 */
period_cycles = c;
/* The duty-cycle value is as follows:
*
* DUTY-CYCLE HIGH LEVEL
* 1 99.9%
* 25 90.0%
* 128 50.0%
* 220 10.0%
* 255 0.1%
* 0 0.0%
*
* In other words, the register value is duty-cycle % 256 with
* duty-cycle in the range 1-256.
*/
c = 256 * duty_ns;
do_div(c, period_ns);
if (c > 255)
c = 255;
duty_cycles = 256 - c;
val = readl(lpc32xx->base + (pwm->hwpwm << 2));
val &= ~0xFFFF;
val |= PWM_RELOADV(period_cycles) | PWM_DUTY(duty_cycles);
writel(val, lpc32xx->base + (pwm->hwpwm << 2));
return 0;
}
static int lpc32xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct lpc32xx_pwm_chip *lpc32xx = to_lpc32xx_pwm_chip(chip);
u32 val;
int ret;
ret = clk_enable(lpc32xx->clk);
if (ret)
return ret;
val = readl(lpc32xx->base + (pwm->hwpwm << 2));
val |= PWM_ENABLE;
writel(val, lpc32xx->base + (pwm->hwpwm << 2));
return 0;
}
static void lpc32xx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct lpc32xx_pwm_chip *lpc32xx = to_lpc32xx_pwm_chip(chip);
u32 val;
val = readl(lpc32xx->base + (pwm->hwpwm << 2));
val &= ~PWM_ENABLE;
writel(val, lpc32xx->base + (pwm->hwpwm << 2));
clk_disable(lpc32xx->clk);
}
static const struct pwm_ops lpc32xx_pwm_ops = {
.config = lpc32xx_pwm_config,
.enable = lpc32xx_pwm_enable,
.disable = lpc32xx_pwm_disable,
.owner = THIS_MODULE,
};
static int lpc32xx_pwm_probe(struct platform_device *pdev)
{
struct lpc32xx_pwm_chip *lpc32xx;
struct resource *res;
int ret;
lpc32xx = devm_kzalloc(&pdev->dev, sizeof(*lpc32xx), GFP_KERNEL);
if (!lpc32xx)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -EINVAL;
lpc32xx->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(lpc32xx->base))
return PTR_ERR(lpc32xx->base);
lpc32xx->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(lpc32xx->clk))
return PTR_ERR(lpc32xx->clk);
lpc32xx->chip.dev = &pdev->dev;
lpc32xx->chip.ops = &lpc32xx_pwm_ops;
lpc32xx->chip.npwm = 2;
lpc32xx->chip.base = -1;
ret = pwmchip_add(&lpc32xx->chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add PWM chip, error %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, lpc32xx);
return 0;
}
static int lpc32xx_pwm_remove(struct platform_device *pdev)
{
struct lpc32xx_pwm_chip *lpc32xx = platform_get_drvdata(pdev);
unsigned int i;
for (i = 0; i < lpc32xx->chip.npwm; i++)
pwm_disable(&lpc32xx->chip.pwms[i]);
return pwmchip_remove(&lpc32xx->chip);
}
static const struct of_device_id lpc32xx_pwm_dt_ids[] = {
{ .compatible = "nxp,lpc3220-pwm", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, lpc32xx_pwm_dt_ids);
static struct platform_driver lpc32xx_pwm_driver = {
.driver = {
.name = "lpc32xx-pwm",
.of_match_table = of_match_ptr(lpc32xx_pwm_dt_ids),
},
.probe = lpc32xx_pwm_probe,
.remove = lpc32xx_pwm_remove,
};
module_platform_driver(lpc32xx_pwm_driver);
MODULE_ALIAS("platform:lpc32xx-pwm");
MODULE_AUTHOR("Alexandre Pereira da Silva <aletes.xgr@gmail.com>");
MODULE_DESCRIPTION("LPC32XX PWM Driver");
MODULE_LICENSE("GPL v2");

201
drivers/pwm/pwm-mxs.c Normal file
View File

@@ -0,0 +1,201 @@
/*
* Copyright 2012 Freescale Semiconductor, Inc.
*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
* Version 2 or later at the following locations:
*
* http://www.opensource.org/licenses/gpl-license.html
* http://www.gnu.org/copyleft/gpl.html
*/
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/stmp_device.h>
#define SET 0x4
#define CLR 0x8
#define TOG 0xc
#define PWM_CTRL 0x0
#define PWM_ACTIVE0 0x10
#define PWM_PERIOD0 0x20
#define PERIOD_PERIOD(p) ((p) & 0xffff)
#define PERIOD_PERIOD_MAX 0x10000
#define PERIOD_ACTIVE_HIGH (3 << 16)
#define PERIOD_INACTIVE_LOW (2 << 18)
#define PERIOD_CDIV(div) (((div) & 0x7) << 20)
#define PERIOD_CDIV_MAX 8
struct mxs_pwm_chip {
struct pwm_chip chip;
struct clk *clk;
void __iomem *base;
};
#define to_mxs_pwm_chip(_chip) container_of(_chip, struct mxs_pwm_chip, chip)
static int mxs_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip);
int ret, div = 0;
unsigned int period_cycles, duty_cycles;
unsigned long rate;
unsigned long long c;
rate = clk_get_rate(mxs->clk);
while (1) {
c = rate / (1 << div);
c = c * period_ns;
do_div(c, 1000000000);
if (c < PERIOD_PERIOD_MAX)
break;
div++;
if (div > PERIOD_CDIV_MAX)
return -EINVAL;
}
period_cycles = c;
c *= duty_ns;
do_div(c, period_ns);
duty_cycles = c;
/*
* If the PWM channel is disabled, make sure to turn on the clock
* before writing the register. Otherwise, keep it enabled.
*/
if (!test_bit(PWMF_ENABLED, &pwm->flags)) {
ret = clk_prepare_enable(mxs->clk);
if (ret)
return ret;
}
writel(duty_cycles << 16,
mxs->base + PWM_ACTIVE0 + pwm->hwpwm * 0x20);
writel(PERIOD_PERIOD(period_cycles) | PERIOD_ACTIVE_HIGH |
PERIOD_INACTIVE_LOW | PERIOD_CDIV(div),
mxs->base + PWM_PERIOD0 + pwm->hwpwm * 0x20);
/*
* If the PWM is not enabled, turn the clock off again to save power.
*/
if (!test_bit(PWMF_ENABLED, &pwm->flags))
clk_disable_unprepare(mxs->clk);
return 0;
}
static int mxs_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip);
int ret;
ret = clk_prepare_enable(mxs->clk);
if (ret)
return ret;
writel(1 << pwm->hwpwm, mxs->base + PWM_CTRL + SET);
return 0;
}
static void mxs_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip);
writel(1 << pwm->hwpwm, mxs->base + PWM_CTRL + CLR);
clk_disable_unprepare(mxs->clk);
}
static const struct pwm_ops mxs_pwm_ops = {
.config = mxs_pwm_config,
.enable = mxs_pwm_enable,
.disable = mxs_pwm_disable,
.owner = THIS_MODULE,
};
static int mxs_pwm_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct mxs_pwm_chip *mxs;
struct resource *res;
struct pinctrl *pinctrl;
int ret;
mxs = devm_kzalloc(&pdev->dev, sizeof(*mxs), GFP_KERNEL);
if (!mxs)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
mxs->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(mxs->base))
return PTR_ERR(mxs->base);
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
if (IS_ERR(pinctrl))
return PTR_ERR(pinctrl);
mxs->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(mxs->clk))
return PTR_ERR(mxs->clk);
mxs->chip.dev = &pdev->dev;
mxs->chip.ops = &mxs_pwm_ops;
mxs->chip.base = -1;
ret = of_property_read_u32(np, "fsl,pwm-number", &mxs->chip.npwm);
if (ret < 0) {
dev_err(&pdev->dev, "failed to get pwm number: %d\n", ret);
return ret;
}
ret = pwmchip_add(&mxs->chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add pwm chip %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, mxs);
stmp_reset_block(mxs->base);
return 0;
}
static int mxs_pwm_remove(struct platform_device *pdev)
{
struct mxs_pwm_chip *mxs = platform_get_drvdata(pdev);
return pwmchip_remove(&mxs->chip);
}
static const struct of_device_id mxs_pwm_dt_ids[] = {
{ .compatible = "fsl,imx23-pwm", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mxs_pwm_dt_ids);
static struct platform_driver mxs_pwm_driver = {
.driver = {
.name = "mxs-pwm",
.of_match_table = of_match_ptr(mxs_pwm_dt_ids),
},
.probe = mxs_pwm_probe,
.remove = mxs_pwm_remove,
};
module_platform_driver(mxs_pwm_driver);
MODULE_ALIAS("platform:mxs-pwm");
MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
MODULE_DESCRIPTION("Freescale MXS PWM Driver");
MODULE_LICENSE("GPL v2");

155
drivers/pwm/pwm-puv3.c Normal file
View File

@@ -0,0 +1,155 @@
/*
* linux/arch/unicore32/kernel/pwm.c
*
* Code specific to PKUnity SoC and UniCore ISA
*
* Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn>
* Copyright (C) 2001-2010 Guan Xuetao
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/pwm.h>
#include <asm/div64.h>
#include <mach/hardware.h>
struct puv3_pwm_chip {
struct pwm_chip chip;
void __iomem *base;
struct clk *clk;
};
static inline struct puv3_pwm_chip *to_puv3(struct pwm_chip *chip)
{
return container_of(chip, struct puv3_pwm_chip, chip);
}
/*
* period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE
* duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
*/
static int puv3_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
unsigned long period_cycles, prescale, pv, dc;
struct puv3_pwm_chip *puv3 = to_puv3(chip);
unsigned long long c;
c = clk_get_rate(puv3->clk);
c = c * period_ns;
do_div(c, 1000000000);
period_cycles = c;
if (period_cycles < 1)
period_cycles = 1;
prescale = (period_cycles - 1) / 1024;
pv = period_cycles / (prescale + 1) - 1;
if (prescale > 63)
return -EINVAL;
if (duty_ns == period_ns)
dc = OST_PWMDCCR_FDCYCLE;
else
dc = (pv + 1) * duty_ns / period_ns;
/*
* NOTE: the clock to PWM has to be enabled first
* before writing to the registers
*/
clk_prepare_enable(puv3->clk);
writel(prescale, puv3->base + OST_PWM_PWCR);
writel(pv - dc, puv3->base + OST_PWM_DCCR);
writel(pv, puv3->base + OST_PWM_PCR);
clk_disable_unprepare(puv3->clk);
return 0;
}
static int puv3_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct puv3_pwm_chip *puv3 = to_puv3(chip);
return clk_prepare_enable(puv3->clk);
}
static void puv3_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct puv3_pwm_chip *puv3 = to_puv3(chip);
clk_disable_unprepare(puv3->clk);
}
static const struct pwm_ops puv3_pwm_ops = {
.config = puv3_pwm_config,
.enable = puv3_pwm_enable,
.disable = puv3_pwm_disable,
.owner = THIS_MODULE,
};
static int pwm_probe(struct platform_device *pdev)
{
struct puv3_pwm_chip *puv3;
struct resource *r;
int ret;
puv3 = devm_kzalloc(&pdev->dev, sizeof(*puv3), GFP_KERNEL);
if (puv3 == NULL) {
dev_err(&pdev->dev, "failed to allocate memory\n");
return -ENOMEM;
}
puv3->clk = devm_clk_get(&pdev->dev, "OST_CLK");
if (IS_ERR(puv3->clk))
return PTR_ERR(puv3->clk);
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
puv3->base = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(puv3->base))
return PTR_ERR(puv3->base);
puv3->chip.dev = &pdev->dev;
puv3->chip.ops = &puv3_pwm_ops;
puv3->chip.base = -1;
puv3->chip.npwm = 1;
ret = pwmchip_add(&puv3->chip);
if (ret < 0) {
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, puv3);
return 0;
}
static int pwm_remove(struct platform_device *pdev)
{
struct puv3_pwm_chip *puv3 = platform_get_drvdata(pdev);
return pwmchip_remove(&puv3->chip);
}
static struct platform_driver puv3_pwm_driver = {
.driver = {
.name = "PKUnity-v3-PWM",
},
.probe = pwm_probe,
.remove = pwm_remove,
};
module_platform_driver(puv3_pwm_driver);
MODULE_LICENSE("GPL v2");

197
drivers/pwm/pwm-pxa.c Normal file
View File

@@ -0,0 +1,197 @@
/*
* drivers/pwm/pwm-pxa.c
*
* simple driver for PWM (Pulse Width Modulator) controller
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 2008-02-13 initial version
* eric miao <eric.miao@marvell.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/pwm.h>
#include <asm/div64.h>
#define HAS_SECONDARY_PWM 0x10
static const struct platform_device_id pwm_id_table[] = {
/* PWM has_secondary_pwm? */
{ "pxa25x-pwm", 0 },
{ "pxa27x-pwm", HAS_SECONDARY_PWM },
{ "pxa168-pwm", 0 },
{ "pxa910-pwm", 0 },
{ },
};
MODULE_DEVICE_TABLE(platform, pwm_id_table);
/* PWM registers and bits definitions */
#define PWMCR (0x00)
#define PWMDCR (0x04)
#define PWMPCR (0x08)
#define PWMCR_SD (1 << 6)
#define PWMDCR_FD (1 << 10)
struct pxa_pwm_chip {
struct pwm_chip chip;
struct device *dev;
struct clk *clk;
void __iomem *mmio_base;
};
static inline struct pxa_pwm_chip *to_pxa_pwm_chip(struct pwm_chip *chip)
{
return container_of(chip, struct pxa_pwm_chip, chip);
}
/*
* period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE
* duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
*/
static int pxa_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip);
unsigned long long c;
unsigned long period_cycles, prescale, pv, dc;
unsigned long offset;
int rc;
offset = pwm->hwpwm ? 0x10 : 0;
c = clk_get_rate(pc->clk);
c = c * period_ns;
do_div(c, 1000000000);
period_cycles = c;
if (period_cycles < 1)
period_cycles = 1;
prescale = (period_cycles - 1) / 1024;
pv = period_cycles / (prescale + 1) - 1;
if (prescale > 63)
return -EINVAL;
if (duty_ns == period_ns)
dc = PWMDCR_FD;
else
dc = (pv + 1) * duty_ns / period_ns;
/* NOTE: the clock to PWM has to be enabled first
* before writing to the registers
*/
rc = clk_prepare_enable(pc->clk);
if (rc < 0)
return rc;
writel(prescale, pc->mmio_base + offset + PWMCR);
writel(dc, pc->mmio_base + offset + PWMDCR);
writel(pv, pc->mmio_base + offset + PWMPCR);
clk_disable_unprepare(pc->clk);
return 0;
}
static int pxa_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip);
return clk_prepare_enable(pc->clk);
}
static void pxa_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip);
clk_disable_unprepare(pc->clk);
}
static struct pwm_ops pxa_pwm_ops = {
.config = pxa_pwm_config,
.enable = pxa_pwm_enable,
.disable = pxa_pwm_disable,
.owner = THIS_MODULE,
};
static int pwm_probe(struct platform_device *pdev)
{
const struct platform_device_id *id = platform_get_device_id(pdev);
struct pxa_pwm_chip *pwm;
struct resource *r;
int ret = 0;
pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
if (pwm == NULL) {
dev_err(&pdev->dev, "failed to allocate memory\n");
return -ENOMEM;
}
pwm->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(pwm->clk))
return PTR_ERR(pwm->clk);
pwm->chip.dev = &pdev->dev;
pwm->chip.ops = &pxa_pwm_ops;
pwm->chip.base = -1;
pwm->chip.npwm = (id->driver_data & HAS_SECONDARY_PWM) ? 2 : 1;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
pwm->mmio_base = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(pwm->mmio_base))
return PTR_ERR(pwm->mmio_base);
ret = pwmchip_add(&pwm->chip);
if (ret < 0) {
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, pwm);
return 0;
}
static int pwm_remove(struct platform_device *pdev)
{
struct pxa_pwm_chip *chip;
chip = platform_get_drvdata(pdev);
if (chip == NULL)
return -ENODEV;
return pwmchip_remove(&chip->chip);
}
static struct platform_driver pwm_driver = {
.driver = {
.name = "pxa25x-pwm",
.owner = THIS_MODULE,
},
.probe = pwm_probe,
.remove = pwm_remove,
.id_table = pwm_id_table,
};
static int __init pwm_init(void)
{
return platform_driver_register(&pwm_driver);
}
arch_initcall(pwm_init);
static void __exit pwm_exit(void)
{
platform_driver_unregister(&pwm_driver);
}
module_exit(pwm_exit);
MODULE_LICENSE("GPL v2");

353
drivers/pwm/pwm-samsung.c Normal file
View File

@@ -0,0 +1,353 @@
/* drivers/pwm/pwm-samsung.c
*
* Copyright (c) 2007 Ben Dooks
* Copyright (c) 2008 Simtec Electronics
* Ben Dooks <ben@simtec.co.uk>, <ben-linux@fluff.org>
*
* S3C series PWM device core
*
* 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 of the License.
*/
#define pr_fmt(fmt) "pwm-samsung: " fmt
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/pwm.h>
#include <mach/map.h>
#include <plat/regs-timer.h>
struct s3c_chip {
struct platform_device *pdev;
struct clk *clk_div;
struct clk *clk;
const char *label;
unsigned int period_ns;
unsigned int duty_ns;
unsigned char tcon_base;
unsigned char pwm_id;
struct pwm_chip chip;
};
#define to_s3c_chip(chip) container_of(chip, struct s3c_chip, chip)
#define pwm_dbg(_pwm, msg...) dev_dbg(&(_pwm)->pdev->dev, msg)
static struct clk *clk_scaler[2];
static inline int pwm_is_tdiv(struct s3c_chip *chip)
{
return clk_get_parent(chip->clk) == chip->clk_div;
}
#define pwm_tcon_start(pwm) (1 << (pwm->tcon_base + 0))
#define pwm_tcon_invert(pwm) (1 << (pwm->tcon_base + 2))
#define pwm_tcon_autoreload(pwm) (1 << (pwm->tcon_base + 3))
#define pwm_tcon_manulupdate(pwm) (1 << (pwm->tcon_base + 1))
static int s3c_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct s3c_chip *s3c = to_s3c_chip(chip);
unsigned long flags;
unsigned long tcon;
local_irq_save(flags);
tcon = __raw_readl(S3C2410_TCON);
tcon |= pwm_tcon_start(s3c);
__raw_writel(tcon, S3C2410_TCON);
local_irq_restore(flags);
return 0;
}
static void s3c_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct s3c_chip *s3c = to_s3c_chip(chip);
unsigned long flags;
unsigned long tcon;
local_irq_save(flags);
tcon = __raw_readl(S3C2410_TCON);
tcon &= ~pwm_tcon_start(s3c);
__raw_writel(tcon, S3C2410_TCON);
local_irq_restore(flags);
}
static unsigned long pwm_calc_tin(struct s3c_chip *s3c, unsigned long freq)
{
unsigned long tin_parent_rate;
unsigned int div;
tin_parent_rate = clk_get_rate(clk_get_parent(s3c->clk_div));
pwm_dbg(s3c, "tin parent at %lu\n", tin_parent_rate);
for (div = 2; div <= 16; div *= 2) {
if ((tin_parent_rate / (div << 16)) < freq)
return tin_parent_rate / div;
}
return tin_parent_rate / 16;
}
#define NS_IN_HZ (1000000000UL)
static int s3c_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct s3c_chip *s3c = to_s3c_chip(chip);
unsigned long tin_rate;
unsigned long tin_ns;
unsigned long period;
unsigned long flags;
unsigned long tcon;
unsigned long tcnt;
long tcmp;
/* We currently avoid using 64bit arithmetic by using the
* fact that anything faster than 1Hz is easily representable
* by 32bits. */
if (period_ns > NS_IN_HZ || duty_ns > NS_IN_HZ)
return -ERANGE;
if (period_ns == s3c->period_ns &&
duty_ns == s3c->duty_ns)
return 0;
/* The TCMP and TCNT can be read without a lock, they're not
* shared between the timers. */
tcmp = __raw_readl(S3C2410_TCMPB(s3c->pwm_id));
tcnt = __raw_readl(S3C2410_TCNTB(s3c->pwm_id));
period = NS_IN_HZ / period_ns;
pwm_dbg(s3c, "duty_ns=%d, period_ns=%d (%lu)\n",
duty_ns, period_ns, period);
/* Check to see if we are changing the clock rate of the PWM */
if (s3c->period_ns != period_ns) {
if (pwm_is_tdiv(s3c)) {
tin_rate = pwm_calc_tin(s3c, period);
clk_set_rate(s3c->clk_div, tin_rate);
} else
tin_rate = clk_get_rate(s3c->clk);
s3c->period_ns = period_ns;
pwm_dbg(s3c, "tin_rate=%lu\n", tin_rate);
tin_ns = NS_IN_HZ / tin_rate;
tcnt = period_ns / tin_ns;
} else
tin_ns = NS_IN_HZ / clk_get_rate(s3c->clk);
/* Note, counters count down */
tcmp = duty_ns / tin_ns;
tcmp = tcnt - tcmp;
/* the pwm hw only checks the compare register after a decrement,
so the pin never toggles if tcmp = tcnt */
if (tcmp == tcnt)
tcmp--;
pwm_dbg(s3c, "tin_ns=%lu, tcmp=%ld/%lu\n", tin_ns, tcmp, tcnt);
if (tcmp < 0)
tcmp = 0;
/* Update the PWM register block. */
local_irq_save(flags);
__raw_writel(tcmp, S3C2410_TCMPB(s3c->pwm_id));
__raw_writel(tcnt, S3C2410_TCNTB(s3c->pwm_id));
tcon = __raw_readl(S3C2410_TCON);
tcon |= pwm_tcon_manulupdate(s3c);
tcon |= pwm_tcon_autoreload(s3c);
__raw_writel(tcon, S3C2410_TCON);
tcon &= ~pwm_tcon_manulupdate(s3c);
__raw_writel(tcon, S3C2410_TCON);
local_irq_restore(flags);
return 0;
}
static struct pwm_ops s3c_pwm_ops = {
.enable = s3c_pwm_enable,
.disable = s3c_pwm_disable,
.config = s3c_pwm_config,
.owner = THIS_MODULE,
};
static int s3c_pwm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct s3c_chip *s3c;
unsigned long flags;
unsigned long tcon;
unsigned int id = pdev->id;
int ret;
if (id == 4) {
dev_err(dev, "TIMER4 is currently not supported\n");
return -ENXIO;
}
s3c = devm_kzalloc(&pdev->dev, sizeof(*s3c), GFP_KERNEL);
if (s3c == NULL) {
dev_err(dev, "failed to allocate pwm_device\n");
return -ENOMEM;
}
/* calculate base of control bits in TCON */
s3c->tcon_base = id == 0 ? 0 : (id * 4) + 4;
s3c->pwm_id = id;
s3c->chip.dev = &pdev->dev;
s3c->chip.ops = &s3c_pwm_ops;
s3c->chip.base = -1;
s3c->chip.npwm = 1;
s3c->clk = devm_clk_get(dev, "pwm-tin");
if (IS_ERR(s3c->clk)) {
dev_err(dev, "failed to get pwm tin clk\n");
return PTR_ERR(s3c->clk);
}
s3c->clk_div = devm_clk_get(dev, "pwm-tdiv");
if (IS_ERR(s3c->clk_div)) {
dev_err(dev, "failed to get pwm tdiv clk\n");
return PTR_ERR(s3c->clk_div);
}
clk_enable(s3c->clk);
clk_enable(s3c->clk_div);
local_irq_save(flags);
tcon = __raw_readl(S3C2410_TCON);
tcon |= pwm_tcon_invert(s3c);
__raw_writel(tcon, S3C2410_TCON);
local_irq_restore(flags);
ret = pwmchip_add(&s3c->chip);
if (ret < 0) {
dev_err(dev, "failed to register pwm\n");
goto err_clk_tdiv;
}
pwm_dbg(s3c, "config bits %02x\n",
(__raw_readl(S3C2410_TCON) >> s3c->tcon_base) & 0x0f);
dev_info(dev, "tin at %lu, tdiv at %lu, tin=%sclk, base %d\n",
clk_get_rate(s3c->clk),
clk_get_rate(s3c->clk_div),
pwm_is_tdiv(s3c) ? "div" : "ext", s3c->tcon_base);
platform_set_drvdata(pdev, s3c);
return 0;
err_clk_tdiv:
clk_disable(s3c->clk_div);
clk_disable(s3c->clk);
return ret;
}
static int s3c_pwm_remove(struct platform_device *pdev)
{
struct s3c_chip *s3c = platform_get_drvdata(pdev);
int err;
err = pwmchip_remove(&s3c->chip);
if (err < 0)
return err;
clk_disable(s3c->clk_div);
clk_disable(s3c->clk);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int s3c_pwm_suspend(struct device *dev)
{
struct s3c_chip *s3c = dev_get_drvdata(dev);
/* No one preserve these values during suspend so reset them
* Otherwise driver leaves PWM unconfigured if same values
* passed to pwm_config
*/
s3c->period_ns = 0;
s3c->duty_ns = 0;
return 0;
}
static int s3c_pwm_resume(struct device *dev)
{
struct s3c_chip *s3c = dev_get_drvdata(dev);
unsigned long tcon;
/* Restore invertion */
tcon = __raw_readl(S3C2410_TCON);
tcon |= pwm_tcon_invert(s3c);
__raw_writel(tcon, S3C2410_TCON);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(s3c_pwm_pm_ops, s3c_pwm_suspend,
s3c_pwm_resume);
static struct platform_driver s3c_pwm_driver = {
.driver = {
.name = "s3c24xx-pwm",
.owner = THIS_MODULE,
.pm = &s3c_pwm_pm_ops,
},
.probe = s3c_pwm_probe,
.remove = s3c_pwm_remove,
};
static int __init pwm_init(void)
{
int ret;
clk_scaler[0] = clk_get(NULL, "pwm-scaler0");
clk_scaler[1] = clk_get(NULL, "pwm-scaler1");
if (IS_ERR(clk_scaler[0]) || IS_ERR(clk_scaler[1])) {
pr_err("failed to get scaler clocks\n");
return -EINVAL;
}
ret = platform_driver_register(&s3c_pwm_driver);
if (ret)
pr_err("failed to add pwm driver\n");
return ret;
}
arch_initcall(pwm_init);

273
drivers/pwm/pwm-spear.c Normal file
View File

@@ -0,0 +1,273 @@
/*
* ST Microelectronics SPEAr Pulse Width Modulator driver
*
* Copyright (C) 2012 ST Microelectronics
* Shiraz Hashim <shiraz.hashim@st.com>
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
* warranty of any kind, whether express or implied.
*/
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/types.h>
#define NUM_PWM 4
/* PWM registers and bits definitions */
#define PWMCR 0x00 /* Control Register */
#define PWMCR_PWM_ENABLE 0x1
#define PWMCR_PRESCALE_SHIFT 2
#define PWMCR_MIN_PRESCALE 0x00
#define PWMCR_MAX_PRESCALE 0x3FFF
#define PWMDCR 0x04 /* Duty Cycle Register */
#define PWMDCR_MIN_DUTY 0x0001
#define PWMDCR_MAX_DUTY 0xFFFF
#define PWMPCR 0x08 /* Period Register */
#define PWMPCR_MIN_PERIOD 0x0001
#define PWMPCR_MAX_PERIOD 0xFFFF
/* Following only available on 13xx SoCs */
#define PWMMCR 0x3C /* Master Control Register */
#define PWMMCR_PWM_ENABLE 0x1
/**
* struct spear_pwm_chip - struct representing pwm chip
*
* @mmio_base: base address of pwm chip
* @clk: pointer to clk structure of pwm chip
* @chip: linux pwm chip representation
*/
struct spear_pwm_chip {
void __iomem *mmio_base;
struct clk *clk;
struct pwm_chip chip;
};
static inline struct spear_pwm_chip *to_spear_pwm_chip(struct pwm_chip *chip)
{
return container_of(chip, struct spear_pwm_chip, chip);
}
static inline u32 spear_pwm_readl(struct spear_pwm_chip *chip, unsigned int num,
unsigned long offset)
{
return readl_relaxed(chip->mmio_base + (num << 4) + offset);
}
static inline void spear_pwm_writel(struct spear_pwm_chip *chip,
unsigned int num, unsigned long offset,
unsigned long val)
{
writel_relaxed(val, chip->mmio_base + (num << 4) + offset);
}
static int spear_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
u64 val, div, clk_rate;
unsigned long prescale = PWMCR_MIN_PRESCALE, pv, dc;
int ret;
/*
* Find pv, dc and prescale to suit duty_ns and period_ns. This is done
* according to formulas described below:
*
* period_ns = 10^9 * (PRESCALE + 1) * PV / PWM_CLK_RATE
* duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
*
* PV = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1))
* DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1))
*/
clk_rate = clk_get_rate(pc->clk);
while (1) {
div = 1000000000;
div *= 1 + prescale;
val = clk_rate * period_ns;
pv = div64_u64(val, div);
val = clk_rate * duty_ns;
dc = div64_u64(val, div);
/* if duty_ns and period_ns are not achievable then return */
if (pv < PWMPCR_MIN_PERIOD || dc < PWMDCR_MIN_DUTY)
return -EINVAL;
/*
* if pv and dc have crossed their upper limit, then increase
* prescale and recalculate pv and dc.
*/
if (pv > PWMPCR_MAX_PERIOD || dc > PWMDCR_MAX_DUTY) {
if (++prescale > PWMCR_MAX_PRESCALE)
return -EINVAL;
continue;
}
break;
}
/*
* NOTE: the clock to PWM has to be enabled first before writing to the
* registers.
*/
ret = clk_enable(pc->clk);
if (ret)
return ret;
spear_pwm_writel(pc, pwm->hwpwm, PWMCR,
prescale << PWMCR_PRESCALE_SHIFT);
spear_pwm_writel(pc, pwm->hwpwm, PWMDCR, dc);
spear_pwm_writel(pc, pwm->hwpwm, PWMPCR, pv);
clk_disable(pc->clk);
return 0;
}
static int spear_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
int rc = 0;
u32 val;
rc = clk_enable(pc->clk);
if (rc)
return rc;
val = spear_pwm_readl(pc, pwm->hwpwm, PWMCR);
val |= PWMCR_PWM_ENABLE;
spear_pwm_writel(pc, pwm->hwpwm, PWMCR, val);
return 0;
}
static void spear_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
u32 val;
val = spear_pwm_readl(pc, pwm->hwpwm, PWMCR);
val &= ~PWMCR_PWM_ENABLE;
spear_pwm_writel(pc, pwm->hwpwm, PWMCR, val);
clk_disable(pc->clk);
}
static const struct pwm_ops spear_pwm_ops = {
.config = spear_pwm_config,
.enable = spear_pwm_enable,
.disable = spear_pwm_disable,
.owner = THIS_MODULE,
};
static int spear_pwm_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct spear_pwm_chip *pc;
struct resource *r;
int ret;
u32 val;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r) {
dev_err(&pdev->dev, "no memory resources defined\n");
return -ENODEV;
}
pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
if (!pc) {
dev_err(&pdev->dev, "failed to allocate memory\n");
return -ENOMEM;
}
pc->mmio_base = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(pc->mmio_base))
return PTR_ERR(pc->mmio_base);
pc->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(pc->clk))
return PTR_ERR(pc->clk);
platform_set_drvdata(pdev, pc);
pc->chip.dev = &pdev->dev;
pc->chip.ops = &spear_pwm_ops;
pc->chip.base = -1;
pc->chip.npwm = NUM_PWM;
ret = clk_prepare(pc->clk);
if (ret)
return ret;
if (of_device_is_compatible(np, "st,spear1340-pwm")) {
ret = clk_enable(pc->clk);
if (ret) {
clk_unprepare(pc->clk);
return ret;
}
/*
* Following enables PWM chip, channels would still be
* enabled individually through their control register
*/
val = readl_relaxed(pc->mmio_base + PWMMCR);
val |= PWMMCR_PWM_ENABLE;
writel_relaxed(val, pc->mmio_base + PWMMCR);
clk_disable(pc->clk);
}
ret = pwmchip_add(&pc->chip);
if (!ret) {
clk_unprepare(pc->clk);
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
}
return ret;
}
static int spear_pwm_remove(struct platform_device *pdev)
{
struct spear_pwm_chip *pc = platform_get_drvdata(pdev);
int i;
for (i = 0; i < NUM_PWM; i++)
pwm_disable(&pc->chip.pwms[i]);
/* clk was prepared in probe, hence unprepare it here */
clk_unprepare(pc->clk);
return pwmchip_remove(&pc->chip);
}
static const struct of_device_id spear_pwm_of_match[] = {
{ .compatible = "st,spear320-pwm" },
{ .compatible = "st,spear1340-pwm" },
{ }
};
MODULE_DEVICE_TABLE(of, spear_pwm_of_match);
static struct platform_driver spear_pwm_driver = {
.driver = {
.name = "spear-pwm",
.of_match_table = spear_pwm_of_match,
},
.probe = spear_pwm_probe,
.remove = spear_pwm_remove,
};
module_platform_driver(spear_pwm_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Shiraz Hashim <shiraz.hashim@st.com>");
MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.com>");
MODULE_ALIAS("platform:spear-pwm");

252
drivers/pwm/pwm-tegra.c Normal file
View File

@@ -0,0 +1,252 @@
/*
* drivers/pwm/pwm-tegra.c
*
* Tegra pulse-width-modulation controller driver
*
* Copyright (c) 2010, NVIDIA Corporation.
* Based on arch/arm/plat-mxc/pwm.c by Sascha Hauer <s.hauer@pengutronix.de>
*
* 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 of the License, 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; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pwm.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#define PWM_ENABLE (1 << 31)
#define PWM_DUTY_WIDTH 8
#define PWM_DUTY_SHIFT 16
#define PWM_SCALE_WIDTH 13
#define PWM_SCALE_SHIFT 0
#define NUM_PWM 4
struct tegra_pwm_chip {
struct pwm_chip chip;
struct device *dev;
struct clk *clk;
void __iomem *mmio_base;
};
static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip)
{
return container_of(chip, struct tegra_pwm_chip, chip);
}
static inline u32 pwm_readl(struct tegra_pwm_chip *chip, unsigned int num)
{
return readl(chip->mmio_base + (num << 4));
}
static inline void pwm_writel(struct tegra_pwm_chip *chip, unsigned int num,
unsigned long val)
{
writel(val, chip->mmio_base + (num << 4));
}
static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
unsigned long long c;
unsigned long rate, hz;
u32 val = 0;
int err;
/*
* Convert from duty_ns / period_ns to a fixed number of duty ticks
* per (1 << PWM_DUTY_WIDTH) cycles and make sure to round to the
* nearest integer during division.
*/
c = duty_ns * (1 << PWM_DUTY_WIDTH) + period_ns / 2;
do_div(c, period_ns);
val = (u32)c << PWM_DUTY_SHIFT;
/*
* Compute the prescaler value for which (1 << PWM_DUTY_WIDTH)
* cycles at the PWM clock rate will take period_ns nanoseconds.
*/
rate = clk_get_rate(pc->clk) >> PWM_DUTY_WIDTH;
hz = 1000000000ul / period_ns;
rate = (rate + (hz / 2)) / hz;
/*
* Since the actual PWM divider is the register's frequency divider
* field minus 1, we need to decrement to get the correct value to
* write to the register.
*/
if (rate > 0)
rate--;
/*
* Make sure that the rate will fit in the register's frequency
* divider field.
*/
if (rate >> PWM_SCALE_WIDTH)
return -EINVAL;
val |= rate << PWM_SCALE_SHIFT;
/*
* If the PWM channel is disabled, make sure to turn on the clock
* before writing the register. Otherwise, keep it enabled.
*/
if (!test_bit(PWMF_ENABLED, &pwm->flags)) {
err = clk_prepare_enable(pc->clk);
if (err < 0)
return err;
} else
val |= PWM_ENABLE;
pwm_writel(pc, pwm->hwpwm, val);
/*
* If the PWM is not enabled, turn the clock off again to save power.
*/
if (!test_bit(PWMF_ENABLED, &pwm->flags))
clk_disable_unprepare(pc->clk);
return 0;
}
static int tegra_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
int rc = 0;
u32 val;
rc = clk_prepare_enable(pc->clk);
if (rc < 0)
return rc;
val = pwm_readl(pc, pwm->hwpwm);
val |= PWM_ENABLE;
pwm_writel(pc, pwm->hwpwm, val);
return 0;
}
static void tegra_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
u32 val;
val = pwm_readl(pc, pwm->hwpwm);
val &= ~PWM_ENABLE;
pwm_writel(pc, pwm->hwpwm, val);
clk_disable_unprepare(pc->clk);
}
static const struct pwm_ops tegra_pwm_ops = {
.config = tegra_pwm_config,
.enable = tegra_pwm_enable,
.disable = tegra_pwm_disable,
.owner = THIS_MODULE,
};
static int tegra_pwm_probe(struct platform_device *pdev)
{
struct tegra_pwm_chip *pwm;
struct resource *r;
int ret;
pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
if (!pwm) {
dev_err(&pdev->dev, "failed to allocate memory\n");
return -ENOMEM;
}
pwm->dev = &pdev->dev;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
pwm->mmio_base = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(pwm->mmio_base))
return PTR_ERR(pwm->mmio_base);
platform_set_drvdata(pdev, pwm);
pwm->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(pwm->clk))
return PTR_ERR(pwm->clk);
pwm->chip.dev = &pdev->dev;
pwm->chip.ops = &tegra_pwm_ops;
pwm->chip.base = -1;
pwm->chip.npwm = NUM_PWM;
ret = pwmchip_add(&pwm->chip);
if (ret < 0) {
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
return ret;
}
return 0;
}
static int tegra_pwm_remove(struct platform_device *pdev)
{
struct tegra_pwm_chip *pc = platform_get_drvdata(pdev);
int i;
if (WARN_ON(!pc))
return -ENODEV;
for (i = 0; i < NUM_PWM; i++) {
struct pwm_device *pwm = &pc->chip.pwms[i];
if (!test_bit(PWMF_ENABLED, &pwm->flags))
if (clk_prepare_enable(pc->clk) < 0)
continue;
pwm_writel(pc, i, 0);
clk_disable_unprepare(pc->clk);
}
return pwmchip_remove(&pc->chip);
}
static const struct of_device_id tegra_pwm_of_match[] = {
{ .compatible = "nvidia,tegra20-pwm" },
{ .compatible = "nvidia,tegra30-pwm" },
{ }
};
MODULE_DEVICE_TABLE(of, tegra_pwm_of_match);
static struct platform_driver tegra_pwm_driver = {
.driver = {
.name = "tegra-pwm",
.of_match_table = tegra_pwm_of_match,
},
.probe = tegra_pwm_probe,
.remove = tegra_pwm_remove,
};
module_platform_driver(tegra_pwm_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("NVIDIA Corporation");
MODULE_ALIAS("platform:tegra-pwm");

355
drivers/pwm/pwm-tiecap.c Normal file
View File

@@ -0,0 +1,355 @@
/*
* ECAP PWM driver
*
* Copyright (C) 2012 Texas Instruments, Inc. - http://www.ti.com/
*
* 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 of the License, 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; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/pm_runtime.h>
#include <linux/pwm.h>
#include <linux/of_device.h>
#include <linux/pinctrl/consumer.h>
#include "pwm-tipwmss.h"
/* ECAP registers and bits definitions */
#define CAP1 0x08
#define CAP2 0x0C
#define CAP3 0x10
#define CAP4 0x14
#define ECCTL2 0x2A
#define ECCTL2_APWM_POL_LOW BIT(10)
#define ECCTL2_APWM_MODE BIT(9)
#define ECCTL2_SYNC_SEL_DISA (BIT(7) | BIT(6))
#define ECCTL2_TSCTR_FREERUN BIT(4)
struct ecap_context {
u32 cap3;
u32 cap4;
u16 ecctl2;
};
struct ecap_pwm_chip {
struct pwm_chip chip;
unsigned int clk_rate;
void __iomem *mmio_base;
struct ecap_context ctx;
};
static inline struct ecap_pwm_chip *to_ecap_pwm_chip(struct pwm_chip *chip)
{
return container_of(chip, struct ecap_pwm_chip, chip);
}
/*
* period_ns = 10^9 * period_cycles / PWM_CLK_RATE
* duty_ns = 10^9 * duty_cycles / PWM_CLK_RATE
*/
static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
unsigned long long c;
unsigned long period_cycles, duty_cycles;
unsigned int reg_val;
if (period_ns > NSEC_PER_SEC)
return -ERANGE;
c = pc->clk_rate;
c = c * period_ns;
do_div(c, NSEC_PER_SEC);
period_cycles = (unsigned long)c;
if (period_cycles < 1) {
period_cycles = 1;
duty_cycles = 1;
} else {
c = pc->clk_rate;
c = c * duty_ns;
do_div(c, NSEC_PER_SEC);
duty_cycles = (unsigned long)c;
}
pm_runtime_get_sync(pc->chip.dev);
reg_val = readw(pc->mmio_base + ECCTL2);
/* Configure APWM mode & disable sync option */
reg_val |= ECCTL2_APWM_MODE | ECCTL2_SYNC_SEL_DISA;
writew(reg_val, pc->mmio_base + ECCTL2);
if (!test_bit(PWMF_ENABLED, &pwm->flags)) {
/* Update active registers if not running */
writel(duty_cycles, pc->mmio_base + CAP2);
writel(period_cycles, pc->mmio_base + CAP1);
} else {
/*
* Update shadow registers to configure period and
* compare values. This helps current PWM period to
* complete on reconfiguring
*/
writel(duty_cycles, pc->mmio_base + CAP4);
writel(period_cycles, pc->mmio_base + CAP3);
}
if (!test_bit(PWMF_ENABLED, &pwm->flags)) {
reg_val = readw(pc->mmio_base + ECCTL2);
/* Disable APWM mode to put APWM output Low */
reg_val &= ~ECCTL2_APWM_MODE;
writew(reg_val, pc->mmio_base + ECCTL2);
}
pm_runtime_put_sync(pc->chip.dev);
return 0;
}
static int ecap_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
enum pwm_polarity polarity)
{
struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
unsigned short reg_val;
pm_runtime_get_sync(pc->chip.dev);
reg_val = readw(pc->mmio_base + ECCTL2);
if (polarity == PWM_POLARITY_INVERSED)
/* Duty cycle defines LOW period of PWM */
reg_val |= ECCTL2_APWM_POL_LOW;
else
/* Duty cycle defines HIGH period of PWM */
reg_val &= ~ECCTL2_APWM_POL_LOW;
writew(reg_val, pc->mmio_base + ECCTL2);
pm_runtime_put_sync(pc->chip.dev);
return 0;
}
static int ecap_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
unsigned int reg_val;
/* Leave clock enabled on enabling PWM */
pm_runtime_get_sync(pc->chip.dev);
/*
* Enable 'Free run Time stamp counter mode' to start counter
* and 'APWM mode' to enable APWM output
*/
reg_val = readw(pc->mmio_base + ECCTL2);
reg_val |= ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE;
writew(reg_val, pc->mmio_base + ECCTL2);
return 0;
}
static void ecap_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
unsigned int reg_val;
/*
* Disable 'Free run Time stamp counter mode' to stop counter
* and 'APWM mode' to put APWM output to low
*/
reg_val = readw(pc->mmio_base + ECCTL2);
reg_val &= ~(ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE);
writew(reg_val, pc->mmio_base + ECCTL2);
/* Disable clock on PWM disable */
pm_runtime_put_sync(pc->chip.dev);
}
static void ecap_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
if (test_bit(PWMF_ENABLED, &pwm->flags)) {
dev_warn(chip->dev, "Removing PWM device without disabling\n");
pm_runtime_put_sync(chip->dev);
}
}
static const struct pwm_ops ecap_pwm_ops = {
.free = ecap_pwm_free,
.config = ecap_pwm_config,
.set_polarity = ecap_pwm_set_polarity,
.enable = ecap_pwm_enable,
.disable = ecap_pwm_disable,
.owner = THIS_MODULE,
};
static const struct of_device_id ecap_of_match[] = {
{ .compatible = "ti,am33xx-ecap" },
{},
};
MODULE_DEVICE_TABLE(of, ecap_of_match);
static int ecap_pwm_probe(struct platform_device *pdev)
{
int ret;
struct resource *r;
struct clk *clk;
struct ecap_pwm_chip *pc;
u16 status;
struct pinctrl *pinctrl;
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
if (IS_ERR(pinctrl))
dev_warn(&pdev->dev, "unable to select pin group\n");
pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
if (!pc) {
dev_err(&pdev->dev, "failed to allocate memory\n");
return -ENOMEM;
}
clk = devm_clk_get(&pdev->dev, "fck");
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "failed to get clock\n");
return PTR_ERR(clk);
}
pc->clk_rate = clk_get_rate(clk);
if (!pc->clk_rate) {
dev_err(&pdev->dev, "failed to get clock rate\n");
return -EINVAL;
}
pc->chip.dev = &pdev->dev;
pc->chip.ops = &ecap_pwm_ops;
pc->chip.of_xlate = of_pwm_xlate_with_flags;
pc->chip.of_pwm_n_cells = 3;
pc->chip.base = -1;
pc->chip.npwm = 1;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
pc->mmio_base = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(pc->mmio_base))
return PTR_ERR(pc->mmio_base);
ret = pwmchip_add(&pc->chip);
if (ret < 0) {
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
return ret;
}
pm_runtime_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
status = pwmss_submodule_state_change(pdev->dev.parent,
PWMSS_ECAPCLK_EN);
if (!(status & PWMSS_ECAPCLK_EN_ACK)) {
dev_err(&pdev->dev, "PWMSS config space clock enable failed\n");
ret = -EINVAL;
goto pwmss_clk_failure;
}
pm_runtime_put_sync(&pdev->dev);
platform_set_drvdata(pdev, pc);
return 0;
pwmss_clk_failure:
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
pwmchip_remove(&pc->chip);
return ret;
}
static int ecap_pwm_remove(struct platform_device *pdev)
{
struct ecap_pwm_chip *pc = platform_get_drvdata(pdev);
pm_runtime_get_sync(&pdev->dev);
/*
* Due to hardware misbehaviour, acknowledge of the stop_req
* is missing. Hence checking of the status bit skipped.
*/
pwmss_submodule_state_change(pdev->dev.parent, PWMSS_ECAPCLK_STOP_REQ);
pm_runtime_put_sync(&pdev->dev);
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
return pwmchip_remove(&pc->chip);
}
static void ecap_pwm_save_context(struct ecap_pwm_chip *pc)
{
pm_runtime_get_sync(pc->chip.dev);
pc->ctx.ecctl2 = readw(pc->mmio_base + ECCTL2);
pc->ctx.cap4 = readl(pc->mmio_base + CAP4);
pc->ctx.cap3 = readl(pc->mmio_base + CAP3);
pm_runtime_put_sync(pc->chip.dev);
}
static void ecap_pwm_restore_context(struct ecap_pwm_chip *pc)
{
writel(pc->ctx.cap3, pc->mmio_base + CAP3);
writel(pc->ctx.cap4, pc->mmio_base + CAP4);
writew(pc->ctx.ecctl2, pc->mmio_base + ECCTL2);
}
#ifdef CONFIG_PM_SLEEP
static int ecap_pwm_suspend(struct device *dev)
{
struct ecap_pwm_chip *pc = dev_get_drvdata(dev);
struct pwm_device *pwm = pc->chip.pwms;
ecap_pwm_save_context(pc);
/* Disable explicitly if PWM is running */
if (test_bit(PWMF_ENABLED, &pwm->flags))
pm_runtime_put_sync(dev);
return 0;
}
static int ecap_pwm_resume(struct device *dev)
{
struct ecap_pwm_chip *pc = dev_get_drvdata(dev);
struct pwm_device *pwm = pc->chip.pwms;
/* Enable explicitly if PWM was running */
if (test_bit(PWMF_ENABLED, &pwm->flags))
pm_runtime_get_sync(dev);
ecap_pwm_restore_context(pc);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(ecap_pwm_pm_ops, ecap_pwm_suspend, ecap_pwm_resume);
static struct platform_driver ecap_pwm_driver = {
.driver = {
.name = "ecap",
.owner = THIS_MODULE,
.of_match_table = ecap_of_match,
.pm = &ecap_pwm_pm_ops,
},
.probe = ecap_pwm_probe,
.remove = ecap_pwm_remove,
};
module_platform_driver(ecap_pwm_driver);
MODULE_DESCRIPTION("ECAP PWM driver");
MODULE_AUTHOR("Texas Instruments");
MODULE_LICENSE("GPL");

613
drivers/pwm/pwm-tiehrpwm.c Normal file
View File

@@ -0,0 +1,613 @@
/*
* EHRPWM PWM driver
*
* Copyright (C) 2012 Texas Instruments, Inc. - http://www.ti.com/
*
* 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 of the License, 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; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/pm_runtime.h>
#include <linux/of_device.h>
#include <linux/pinctrl/consumer.h>
#include "pwm-tipwmss.h"
/* EHRPWM registers and bits definitions */
/* Time base module registers */
#define TBCTL 0x00
#define TBPRD 0x0A
#define TBCTL_RUN_MASK (BIT(15) | BIT(14))
#define TBCTL_STOP_NEXT 0
#define TBCTL_STOP_ON_CYCLE BIT(14)
#define TBCTL_FREE_RUN (BIT(15) | BIT(14))
#define TBCTL_PRDLD_MASK BIT(3)
#define TBCTL_PRDLD_SHDW 0
#define TBCTL_PRDLD_IMDT BIT(3)
#define TBCTL_CLKDIV_MASK (BIT(12) | BIT(11) | BIT(10) | BIT(9) | \
BIT(8) | BIT(7))
#define TBCTL_CTRMODE_MASK (BIT(1) | BIT(0))
#define TBCTL_CTRMODE_UP 0
#define TBCTL_CTRMODE_DOWN BIT(0)
#define TBCTL_CTRMODE_UPDOWN BIT(1)
#define TBCTL_CTRMODE_FREEZE (BIT(1) | BIT(0))
#define TBCTL_HSPCLKDIV_SHIFT 7
#define TBCTL_CLKDIV_SHIFT 10
#define CLKDIV_MAX 7
#define HSPCLKDIV_MAX 7
#define PERIOD_MAX 0xFFFF
/* compare module registers */
#define CMPA 0x12
#define CMPB 0x14
/* Action qualifier module registers */
#define AQCTLA 0x16
#define AQCTLB 0x18
#define AQSFRC 0x1A
#define AQCSFRC 0x1C
#define AQCTL_CBU_MASK (BIT(9) | BIT(8))
#define AQCTL_CBU_FRCLOW BIT(8)
#define AQCTL_CBU_FRCHIGH BIT(9)
#define AQCTL_CBU_FRCTOGGLE (BIT(9) | BIT(8))
#define AQCTL_CAU_MASK (BIT(5) | BIT(4))
#define AQCTL_CAU_FRCLOW BIT(4)
#define AQCTL_CAU_FRCHIGH BIT(5)
#define AQCTL_CAU_FRCTOGGLE (BIT(5) | BIT(4))
#define AQCTL_PRD_MASK (BIT(3) | BIT(2))
#define AQCTL_PRD_FRCLOW BIT(2)
#define AQCTL_PRD_FRCHIGH BIT(3)
#define AQCTL_PRD_FRCTOGGLE (BIT(3) | BIT(2))
#define AQCTL_ZRO_MASK (BIT(1) | BIT(0))
#define AQCTL_ZRO_FRCLOW BIT(0)
#define AQCTL_ZRO_FRCHIGH BIT(1)
#define AQCTL_ZRO_FRCTOGGLE (BIT(1) | BIT(0))
#define AQCTL_CHANA_POLNORMAL (AQCTL_CAU_FRCLOW | AQCTL_PRD_FRCHIGH | \
AQCTL_ZRO_FRCHIGH)
#define AQCTL_CHANA_POLINVERSED (AQCTL_CAU_FRCHIGH | AQCTL_PRD_FRCLOW | \
AQCTL_ZRO_FRCLOW)
#define AQCTL_CHANB_POLNORMAL (AQCTL_CBU_FRCLOW | AQCTL_PRD_FRCHIGH | \
AQCTL_ZRO_FRCHIGH)
#define AQCTL_CHANB_POLINVERSED (AQCTL_CBU_FRCHIGH | AQCTL_PRD_FRCLOW | \
AQCTL_ZRO_FRCLOW)
#define AQSFRC_RLDCSF_MASK (BIT(7) | BIT(6))
#define AQSFRC_RLDCSF_ZRO 0
#define AQSFRC_RLDCSF_PRD BIT(6)
#define AQSFRC_RLDCSF_ZROPRD BIT(7)
#define AQSFRC_RLDCSF_IMDT (BIT(7) | BIT(6))
#define AQCSFRC_CSFB_MASK (BIT(3) | BIT(2))
#define AQCSFRC_CSFB_FRCDIS 0
#define AQCSFRC_CSFB_FRCLOW BIT(2)
#define AQCSFRC_CSFB_FRCHIGH BIT(3)
#define AQCSFRC_CSFB_DISSWFRC (BIT(3) | BIT(2))
#define AQCSFRC_CSFA_MASK (BIT(1) | BIT(0))
#define AQCSFRC_CSFA_FRCDIS 0
#define AQCSFRC_CSFA_FRCLOW BIT(0)
#define AQCSFRC_CSFA_FRCHIGH BIT(1)
#define AQCSFRC_CSFA_DISSWFRC (BIT(1) | BIT(0))
#define NUM_PWM_CHANNEL 2 /* EHRPWM channels */
struct ehrpwm_context {
u16 tbctl;
u16 tbprd;
u16 cmpa;
u16 cmpb;
u16 aqctla;
u16 aqctlb;
u16 aqsfrc;
u16 aqcsfrc;
};
struct ehrpwm_pwm_chip {
struct pwm_chip chip;
unsigned int clk_rate;
void __iomem *mmio_base;
unsigned long period_cycles[NUM_PWM_CHANNEL];
enum pwm_polarity polarity[NUM_PWM_CHANNEL];
struct clk *tbclk;
struct ehrpwm_context ctx;
};
static inline struct ehrpwm_pwm_chip *to_ehrpwm_pwm_chip(struct pwm_chip *chip)
{
return container_of(chip, struct ehrpwm_pwm_chip, chip);
}
static u16 ehrpwm_read(void *base, int offset)
{
return readw(base + offset);
}
static void ehrpwm_write(void *base, int offset, unsigned int val)
{
writew(val & 0xFFFF, base + offset);
}
static void ehrpwm_modify(void *base, int offset,
unsigned short mask, unsigned short val)
{
unsigned short regval;
regval = readw(base + offset);
regval &= ~mask;
regval |= val & mask;
writew(regval, base + offset);
}
/**
* set_prescale_div - Set up the prescaler divider function
* @rqst_prescaler: prescaler value min
* @prescale_div: prescaler value set
* @tb_clk_div: Time Base Control prescaler bits
*/
static int set_prescale_div(unsigned long rqst_prescaler,
unsigned short *prescale_div, unsigned short *tb_clk_div)
{
unsigned int clkdiv, hspclkdiv;
for (clkdiv = 0; clkdiv <= CLKDIV_MAX; clkdiv++) {
for (hspclkdiv = 0; hspclkdiv <= HSPCLKDIV_MAX; hspclkdiv++) {
/*
* calculations for prescaler value :
* prescale_div = HSPCLKDIVIDER * CLKDIVIDER.
* HSPCLKDIVIDER = 2 ** hspclkdiv
* CLKDIVIDER = (1), if clkdiv == 0 *OR*
* (2 * clkdiv), if clkdiv != 0
*
* Configure prescale_div value such that period
* register value is less than 65535.
*/
*prescale_div = (1 << clkdiv) *
(hspclkdiv ? (hspclkdiv * 2) : 1);
if (*prescale_div > rqst_prescaler) {
*tb_clk_div = (clkdiv << TBCTL_CLKDIV_SHIFT) |
(hspclkdiv << TBCTL_HSPCLKDIV_SHIFT);
return 0;
}
}
}
return 1;
}
static void configure_polarity(struct ehrpwm_pwm_chip *pc, int chan)
{
int aqctl_reg;
unsigned short aqctl_val, aqctl_mask;
/*
* Configure PWM output to HIGH/LOW level on counter
* reaches compare register value and LOW/HIGH level
* on counter value reaches period register value and
* zero value on counter
*/
if (chan == 1) {
aqctl_reg = AQCTLB;
aqctl_mask = AQCTL_CBU_MASK;
if (pc->polarity[chan] == PWM_POLARITY_INVERSED)
aqctl_val = AQCTL_CHANB_POLINVERSED;
else
aqctl_val = AQCTL_CHANB_POLNORMAL;
} else {
aqctl_reg = AQCTLA;
aqctl_mask = AQCTL_CAU_MASK;
if (pc->polarity[chan] == PWM_POLARITY_INVERSED)
aqctl_val = AQCTL_CHANA_POLINVERSED;
else
aqctl_val = AQCTL_CHANA_POLNORMAL;
}
aqctl_mask |= AQCTL_PRD_MASK | AQCTL_ZRO_MASK;
ehrpwm_modify(pc->mmio_base, aqctl_reg, aqctl_mask, aqctl_val);
}
/*
* period_ns = 10^9 * (ps_divval * period_cycles) / PWM_CLK_RATE
* duty_ns = 10^9 * (ps_divval * duty_cycles) / PWM_CLK_RATE
*/
static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
unsigned long long c;
unsigned long period_cycles, duty_cycles;
unsigned short ps_divval, tb_divval;
int i, cmp_reg;
if (period_ns > NSEC_PER_SEC)
return -ERANGE;
c = pc->clk_rate;
c = c * period_ns;
do_div(c, NSEC_PER_SEC);
period_cycles = (unsigned long)c;
if (period_cycles < 1) {
period_cycles = 1;
duty_cycles = 1;
} else {
c = pc->clk_rate;
c = c * duty_ns;
do_div(c, NSEC_PER_SEC);
duty_cycles = (unsigned long)c;
}
/*
* Period values should be same for multiple PWM channels as IP uses
* same period register for multiple channels.
*/
for (i = 0; i < NUM_PWM_CHANNEL; i++) {
if (pc->period_cycles[i] &&
(pc->period_cycles[i] != period_cycles)) {
/*
* Allow channel to reconfigure period if no other
* channels being configured.
*/
if (i == pwm->hwpwm)
continue;
dev_err(chip->dev, "Period value conflicts with channel %d\n",
i);
return -EINVAL;
}
}
pc->period_cycles[pwm->hwpwm] = period_cycles;
/* Configure clock prescaler to support Low frequency PWM wave */
if (set_prescale_div(period_cycles/PERIOD_MAX, &ps_divval,
&tb_divval)) {
dev_err(chip->dev, "Unsupported values\n");
return -EINVAL;
}
pm_runtime_get_sync(chip->dev);
/* Update clock prescaler values */
ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_CLKDIV_MASK, tb_divval);
/* Update period & duty cycle with presacler division */
period_cycles = period_cycles / ps_divval;
duty_cycles = duty_cycles / ps_divval;
/* Configure shadow loading on Period register */
ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_PRDLD_MASK, TBCTL_PRDLD_SHDW);
ehrpwm_write(pc->mmio_base, TBPRD, period_cycles);
/* Configure ehrpwm counter for up-count mode */
ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_CTRMODE_MASK,
TBCTL_CTRMODE_UP);
if (pwm->hwpwm == 1)
/* Channel 1 configured with compare B register */
cmp_reg = CMPB;
else
/* Channel 0 configured with compare A register */
cmp_reg = CMPA;
ehrpwm_write(pc->mmio_base, cmp_reg, duty_cycles);
pm_runtime_put_sync(chip->dev);
return 0;
}
static int ehrpwm_pwm_set_polarity(struct pwm_chip *chip,
struct pwm_device *pwm, enum pwm_polarity polarity)
{
struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
/* Configuration of polarity in hardware delayed, do at enable */
pc->polarity[pwm->hwpwm] = polarity;
return 0;
}
static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
unsigned short aqcsfrc_val, aqcsfrc_mask;
int ret;
/* Leave clock enabled on enabling PWM */
pm_runtime_get_sync(chip->dev);
/* Disabling Action Qualifier on PWM output */
if (pwm->hwpwm) {
aqcsfrc_val = AQCSFRC_CSFB_FRCDIS;
aqcsfrc_mask = AQCSFRC_CSFB_MASK;
} else {
aqcsfrc_val = AQCSFRC_CSFA_FRCDIS;
aqcsfrc_mask = AQCSFRC_CSFA_MASK;
}
/* Changes to shadow mode */
ehrpwm_modify(pc->mmio_base, AQSFRC, AQSFRC_RLDCSF_MASK,
AQSFRC_RLDCSF_ZRO);
ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val);
/* Channels polarity can be configured from action qualifier module */
configure_polarity(pc, pwm->hwpwm);
/* Enable TBCLK before enabling PWM device */
ret = clk_prepare_enable(pc->tbclk);
if (ret) {
pr_err("Failed to enable TBCLK for %s\n",
dev_name(pc->chip.dev));
return ret;
}
/* Enable time counter for free_run */
ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_FREE_RUN);
return 0;
}
static void ehrpwm_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
unsigned short aqcsfrc_val, aqcsfrc_mask;
/* Action Qualifier puts PWM output low forcefully */
if (pwm->hwpwm) {
aqcsfrc_val = AQCSFRC_CSFB_FRCLOW;
aqcsfrc_mask = AQCSFRC_CSFB_MASK;
} else {
aqcsfrc_val = AQCSFRC_CSFA_FRCLOW;
aqcsfrc_mask = AQCSFRC_CSFA_MASK;
}
/*
* Changes to immediate action on Action Qualifier. This puts
* Action Qualifier control on PWM output from next TBCLK
*/
ehrpwm_modify(pc->mmio_base, AQSFRC, AQSFRC_RLDCSF_MASK,
AQSFRC_RLDCSF_IMDT);
ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val);
/* Disabling TBCLK on PWM disable */
clk_disable_unprepare(pc->tbclk);
/* Stop Time base counter */
ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_STOP_NEXT);
/* Disable clock on PWM disable */
pm_runtime_put_sync(chip->dev);
}
static void ehrpwm_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
if (test_bit(PWMF_ENABLED, &pwm->flags)) {
dev_warn(chip->dev, "Removing PWM device without disabling\n");
pm_runtime_put_sync(chip->dev);
}
/* set period value to zero on free */
pc->period_cycles[pwm->hwpwm] = 0;
}
static const struct pwm_ops ehrpwm_pwm_ops = {
.free = ehrpwm_pwm_free,
.config = ehrpwm_pwm_config,
.set_polarity = ehrpwm_pwm_set_polarity,
.enable = ehrpwm_pwm_enable,
.disable = ehrpwm_pwm_disable,
.owner = THIS_MODULE,
};
static const struct of_device_id ehrpwm_of_match[] = {
{ .compatible = "ti,am33xx-ehrpwm" },
{},
};
MODULE_DEVICE_TABLE(of, ehrpwm_of_match);
static int ehrpwm_pwm_probe(struct platform_device *pdev)
{
int ret;
struct resource *r;
struct clk *clk;
struct ehrpwm_pwm_chip *pc;
u16 status;
struct pinctrl *pinctrl;
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
if (IS_ERR(pinctrl))
dev_warn(&pdev->dev, "unable to select pin group\n");
pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
if (!pc) {
dev_err(&pdev->dev, "failed to allocate memory\n");
return -ENOMEM;
}
clk = devm_clk_get(&pdev->dev, "fck");
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "failed to get clock\n");
return PTR_ERR(clk);
}
pc->clk_rate = clk_get_rate(clk);
if (!pc->clk_rate) {
dev_err(&pdev->dev, "failed to get clock rate\n");
return -EINVAL;
}
pc->chip.dev = &pdev->dev;
pc->chip.ops = &ehrpwm_pwm_ops;
pc->chip.of_xlate = of_pwm_xlate_with_flags;
pc->chip.of_pwm_n_cells = 3;
pc->chip.base = -1;
pc->chip.npwm = NUM_PWM_CHANNEL;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
pc->mmio_base = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(pc->mmio_base))
return PTR_ERR(pc->mmio_base);
/* Acquire tbclk for Time Base EHRPWM submodule */
pc->tbclk = devm_clk_get(&pdev->dev, "tbclk");
if (IS_ERR(pc->tbclk)) {
dev_err(&pdev->dev, "Failed to get tbclk\n");
return PTR_ERR(pc->tbclk);
}
ret = pwmchip_add(&pc->chip);
if (ret < 0) {
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
return ret;
}
pm_runtime_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
status = pwmss_submodule_state_change(pdev->dev.parent,
PWMSS_EPWMCLK_EN);
if (!(status & PWMSS_EPWMCLK_EN_ACK)) {
dev_err(&pdev->dev, "PWMSS config space clock enable failed\n");
ret = -EINVAL;
goto pwmss_clk_failure;
}
pm_runtime_put_sync(&pdev->dev);
platform_set_drvdata(pdev, pc);
return 0;
pwmss_clk_failure:
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
pwmchip_remove(&pc->chip);
return ret;
}
static int ehrpwm_pwm_remove(struct platform_device *pdev)
{
struct ehrpwm_pwm_chip *pc = platform_get_drvdata(pdev);
pm_runtime_get_sync(&pdev->dev);
/*
* Due to hardware misbehaviour, acknowledge of the stop_req
* is missing. Hence checking of the status bit skipped.
*/
pwmss_submodule_state_change(pdev->dev.parent, PWMSS_EPWMCLK_STOP_REQ);
pm_runtime_put_sync(&pdev->dev);
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
return pwmchip_remove(&pc->chip);
}
static void ehrpwm_pwm_save_context(struct ehrpwm_pwm_chip *pc)
{
pm_runtime_get_sync(pc->chip.dev);
pc->ctx.tbctl = ehrpwm_read(pc->mmio_base, TBCTL);
pc->ctx.tbprd = ehrpwm_read(pc->mmio_base, TBPRD);
pc->ctx.cmpa = ehrpwm_read(pc->mmio_base, CMPA);
pc->ctx.cmpb = ehrpwm_read(pc->mmio_base, CMPB);
pc->ctx.aqctla = ehrpwm_read(pc->mmio_base, AQCTLA);
pc->ctx.aqctlb = ehrpwm_read(pc->mmio_base, AQCTLB);
pc->ctx.aqsfrc = ehrpwm_read(pc->mmio_base, AQSFRC);
pc->ctx.aqcsfrc = ehrpwm_read(pc->mmio_base, AQCSFRC);
pm_runtime_put_sync(pc->chip.dev);
}
static void ehrpwm_pwm_restore_context(struct ehrpwm_pwm_chip *pc)
{
ehrpwm_write(pc->mmio_base, TBPRD, pc->ctx.tbprd);
ehrpwm_write(pc->mmio_base, CMPA, pc->ctx.cmpa);
ehrpwm_write(pc->mmio_base, CMPB, pc->ctx.cmpb);
ehrpwm_write(pc->mmio_base, AQCTLA, pc->ctx.aqctla);
ehrpwm_write(pc->mmio_base, AQCTLB, pc->ctx.aqctlb);
ehrpwm_write(pc->mmio_base, AQSFRC, pc->ctx.aqsfrc);
ehrpwm_write(pc->mmio_base, AQCSFRC, pc->ctx.aqcsfrc);
ehrpwm_write(pc->mmio_base, TBCTL, pc->ctx.tbctl);
}
#ifdef CONFIG_PM_SLEEP
static int ehrpwm_pwm_suspend(struct device *dev)
{
struct ehrpwm_pwm_chip *pc = dev_get_drvdata(dev);
int i;
ehrpwm_pwm_save_context(pc);
for (i = 0; i < pc->chip.npwm; i++) {
struct pwm_device *pwm = &pc->chip.pwms[i];
if (!test_bit(PWMF_ENABLED, &pwm->flags))
continue;
/* Disable explicitly if PWM is running */
pm_runtime_put_sync(dev);
}
return 0;
}
static int ehrpwm_pwm_resume(struct device *dev)
{
struct ehrpwm_pwm_chip *pc = dev_get_drvdata(dev);
int i;
for (i = 0; i < pc->chip.npwm; i++) {
struct pwm_device *pwm = &pc->chip.pwms[i];
if (!test_bit(PWMF_ENABLED, &pwm->flags))
continue;
/* Enable explicitly if PWM was running */
pm_runtime_get_sync(dev);
}
ehrpwm_pwm_restore_context(pc);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(ehrpwm_pwm_pm_ops, ehrpwm_pwm_suspend,
ehrpwm_pwm_resume);
static struct platform_driver ehrpwm_pwm_driver = {
.driver = {
.name = "ehrpwm",
.owner = THIS_MODULE,
.of_match_table = ehrpwm_of_match,
.pm = &ehrpwm_pwm_pm_ops,
},
.probe = ehrpwm_pwm_probe,
.remove = ehrpwm_pwm_remove,
};
module_platform_driver(ehrpwm_pwm_driver);
MODULE_DESCRIPTION("EHRPWM PWM driver");
MODULE_AUTHOR("Texas Instruments");
MODULE_LICENSE("GPL");

136
drivers/pwm/pwm-tipwmss.c Normal file
View File

@@ -0,0 +1,136 @@
/*
* TI PWM Subsystem driver
*
* Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/
*
* 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 of the License, 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.
*
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/pm_runtime.h>
#include <linux/of_device.h>
#include "pwm-tipwmss.h"
#define PWMSS_CLKCONFIG 0x8 /* Clock gating reg */
#define PWMSS_CLKSTATUS 0xc /* Clock gating status reg */
struct pwmss_info {
void __iomem *mmio_base;
struct mutex pwmss_lock;
u16 pwmss_clkconfig;
};
u16 pwmss_submodule_state_change(struct device *dev, int set)
{
struct pwmss_info *info = dev_get_drvdata(dev);
u16 val;
mutex_lock(&info->pwmss_lock);
val = readw(info->mmio_base + PWMSS_CLKCONFIG);
val |= set;
writew(val , info->mmio_base + PWMSS_CLKCONFIG);
mutex_unlock(&info->pwmss_lock);
return readw(info->mmio_base + PWMSS_CLKSTATUS);
}
EXPORT_SYMBOL(pwmss_submodule_state_change);
static const struct of_device_id pwmss_of_match[] = {
{ .compatible = "ti,am33xx-pwmss" },
{},
};
MODULE_DEVICE_TABLE(of, pwmss_of_match);
static int pwmss_probe(struct platform_device *pdev)
{
int ret;
struct resource *r;
struct pwmss_info *info;
struct device_node *node = pdev->dev.of_node;
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info) {
dev_err(&pdev->dev, "failed to allocate memory\n");
return -ENOMEM;
}
mutex_init(&info->pwmss_lock);
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
info->mmio_base = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(info->mmio_base))
return PTR_ERR(info->mmio_base);
pm_runtime_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
platform_set_drvdata(pdev, info);
/* Populate all the child nodes here... */
ret = of_platform_populate(node, NULL, NULL, &pdev->dev);
if (ret)
dev_err(&pdev->dev, "no child node found\n");
return ret;
}
static int pwmss_remove(struct platform_device *pdev)
{
struct pwmss_info *info = platform_get_drvdata(pdev);
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
mutex_destroy(&info->pwmss_lock);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int pwmss_suspend(struct device *dev)
{
struct pwmss_info *info = dev_get_drvdata(dev);
info->pwmss_clkconfig = readw(info->mmio_base + PWMSS_CLKCONFIG);
pm_runtime_put_sync(dev);
return 0;
}
static int pwmss_resume(struct device *dev)
{
struct pwmss_info *info = dev_get_drvdata(dev);
pm_runtime_get_sync(dev);
writew(info->pwmss_clkconfig, info->mmio_base + PWMSS_CLKCONFIG);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(pwmss_pm_ops, pwmss_suspend, pwmss_resume);
static struct platform_driver pwmss_driver = {
.driver = {
.name = "pwmss",
.owner = THIS_MODULE,
.pm = &pwmss_pm_ops,
.of_match_table = pwmss_of_match,
},
.probe = pwmss_probe,
.remove = pwmss_remove,
};
module_platform_driver(pwmss_driver);
MODULE_DESCRIPTION("PWM Subsystem driver");
MODULE_AUTHOR("Texas Instruments");
MODULE_LICENSE("GPL");

39
drivers/pwm/pwm-tipwmss.h Normal file
View File

@@ -0,0 +1,39 @@
/*
* TI PWM Subsystem driver
*
* Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/
*
* 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 of the License, 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.
*
*/
#ifndef __TIPWMSS_H
#define __TIPWMSS_H
/* PWM substem clock gating */
#define PWMSS_ECAPCLK_EN BIT(0)
#define PWMSS_ECAPCLK_STOP_REQ BIT(1)
#define PWMSS_EPWMCLK_EN BIT(8)
#define PWMSS_EPWMCLK_STOP_REQ BIT(9)
#define PWMSS_ECAPCLK_EN_ACK BIT(0)
#define PWMSS_EPWMCLK_EN_ACK BIT(8)
#ifdef CONFIG_PWM_TIPWMSS
extern u16 pwmss_submodule_state_change(struct device *dev, int set);
#else
static inline u16 pwmss_submodule_state_change(struct device *dev, int set)
{
/* return success status value */
return 0xFFFF;
}
#endif
#endif /* __TIPWMSS_H */

347
drivers/pwm/pwm-twl-led.c Normal file
View File

@@ -0,0 +1,347 @@
/*
* Driver for TWL4030/6030 Pulse Width Modulator used as LED driver
*
* Copyright (C) 2012 Texas Instruments
* Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
*
* This driver is a complete rewrite of the former pwm-twl6030.c authorded by:
* Hemanth V <hemanthv@ti.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* 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. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/i2c/twl.h>
#include <linux/slab.h>
/*
* This driver handles the PWM driven LED terminals of TWL4030 and TWL6030.
* To generate the signal on TWL4030:
* - LEDA uses PWMA
* - LEDB uses PWMB
* TWL6030 has one LED pin with dedicated LEDPWM
*/
#define TWL4030_LED_MAX 0x7f
#define TWL6030_LED_MAX 0xff
/* Registers, bits and macro for TWL4030 */
#define TWL4030_LEDEN_REG 0x00
#define TWL4030_PWMA_REG 0x01
#define TWL4030_LEDXON (1 << 0)
#define TWL4030_LEDXPWM (1 << 4)
#define TWL4030_LED_PINS (TWL4030_LEDXON | TWL4030_LEDXPWM)
#define TWL4030_LED_TOGGLE(led, x) ((x) << (led))
/* Register, bits and macro for TWL6030 */
#define TWL6030_LED_PWM_CTRL1 0xf4
#define TWL6030_LED_PWM_CTRL2 0xf5
#define TWL6040_LED_MODE_HW 0x00
#define TWL6040_LED_MODE_ON 0x01
#define TWL6040_LED_MODE_OFF 0x02
#define TWL6040_LED_MODE_MASK 0x03
struct twl_pwmled_chip {
struct pwm_chip chip;
struct mutex mutex;
};
static inline struct twl_pwmled_chip *to_twl(struct pwm_chip *chip)
{
return container_of(chip, struct twl_pwmled_chip, chip);
}
static int twl4030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
int duty_cycle = DIV_ROUND_UP(duty_ns * TWL4030_LED_MAX, period_ns) + 1;
u8 pwm_config[2] = { 1, 0 };
int base, ret;
/*
* To configure the duty period:
* On-cycle is set to 1 (the minimum allowed value)
* The off time of 0 is not configurable, so the mapping is:
* 0 -> off cycle = 2,
* 1 -> off cycle = 2,
* 2 -> off cycle = 3,
* 126 - > off cycle 127,
* 127 - > off cycle 1
* When on cycle == off cycle the PWM will be always on
*/
if (duty_cycle == 1)
duty_cycle = 2;
else if (duty_cycle > TWL4030_LED_MAX)
duty_cycle = 1;
base = pwm->hwpwm * 2 + TWL4030_PWMA_REG;
pwm_config[1] = duty_cycle;
ret = twl_i2c_write(TWL4030_MODULE_LED, pwm_config, base, 2);
if (ret < 0)
dev_err(chip->dev, "%s: Failed to configure PWM\n", pwm->label);
return ret;
}
static int twl4030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct twl_pwmled_chip *twl = to_twl(chip);
int ret;
u8 val;
mutex_lock(&twl->mutex);
ret = twl_i2c_read_u8(TWL4030_MODULE_LED, &val, TWL4030_LEDEN_REG);
if (ret < 0) {
dev_err(chip->dev, "%s: Failed to read LEDEN\n", pwm->label);
goto out;
}
val |= TWL4030_LED_TOGGLE(pwm->hwpwm, TWL4030_LED_PINS);
ret = twl_i2c_write_u8(TWL4030_MODULE_LED, val, TWL4030_LEDEN_REG);
if (ret < 0)
dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
out:
mutex_unlock(&twl->mutex);
return ret;
}
static void twl4030_pwmled_disable(struct pwm_chip *chip,
struct pwm_device *pwm)
{
struct twl_pwmled_chip *twl = to_twl(chip);
int ret;
u8 val;
mutex_lock(&twl->mutex);
ret = twl_i2c_read_u8(TWL4030_MODULE_LED, &val, TWL4030_LEDEN_REG);
if (ret < 0) {
dev_err(chip->dev, "%s: Failed to read LEDEN\n", pwm->label);
goto out;
}
val &= ~TWL4030_LED_TOGGLE(pwm->hwpwm, TWL4030_LED_PINS);
ret = twl_i2c_write_u8(TWL4030_MODULE_LED, val, TWL4030_LEDEN_REG);
if (ret < 0)
dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
out:
mutex_unlock(&twl->mutex);
}
static int twl6030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
int duty_cycle = (duty_ns * TWL6030_LED_MAX) / period_ns;
u8 on_time;
int ret;
on_time = duty_cycle & 0xff;
ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, on_time,
TWL6030_LED_PWM_CTRL1);
if (ret < 0)
dev_err(chip->dev, "%s: Failed to configure PWM\n", pwm->label);
return ret;
}
static int twl6030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct twl_pwmled_chip *twl = to_twl(chip);
int ret;
u8 val;
mutex_lock(&twl->mutex);
ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2);
if (ret < 0) {
dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n",
pwm->label);
goto out;
}
val &= ~TWL6040_LED_MODE_MASK;
val |= TWL6040_LED_MODE_ON;
ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2);
if (ret < 0)
dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
out:
mutex_unlock(&twl->mutex);
return ret;
}
static void twl6030_pwmled_disable(struct pwm_chip *chip,
struct pwm_device *pwm)
{
struct twl_pwmled_chip *twl = to_twl(chip);
int ret;
u8 val;
mutex_lock(&twl->mutex);
ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2);
if (ret < 0) {
dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n",
pwm->label);
goto out;
}
val &= ~TWL6040_LED_MODE_MASK;
val |= TWL6040_LED_MODE_OFF;
ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2);
if (ret < 0)
dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
out:
mutex_unlock(&twl->mutex);
}
static int twl6030_pwmled_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct twl_pwmled_chip *twl = to_twl(chip);
int ret;
u8 val;
mutex_lock(&twl->mutex);
ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2);
if (ret < 0) {
dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n",
pwm->label);
goto out;
}
val &= ~TWL6040_LED_MODE_MASK;
val |= TWL6040_LED_MODE_OFF;
ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2);
if (ret < 0)
dev_err(chip->dev, "%s: Failed to request PWM\n", pwm->label);
out:
mutex_unlock(&twl->mutex);
return ret;
}
static void twl6030_pwmled_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct twl_pwmled_chip *twl = to_twl(chip);
int ret;
u8 val;
mutex_lock(&twl->mutex);
ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2);
if (ret < 0) {
dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n",
pwm->label);
goto out;
}
val &= ~TWL6040_LED_MODE_MASK;
val |= TWL6040_LED_MODE_HW;
ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2);
if (ret < 0)
dev_err(chip->dev, "%s: Failed to free PWM\n", pwm->label);
out:
mutex_unlock(&twl->mutex);
}
static const struct pwm_ops twl4030_pwmled_ops = {
.enable = twl4030_pwmled_enable,
.disable = twl4030_pwmled_disable,
.config = twl4030_pwmled_config,
.owner = THIS_MODULE,
};
static const struct pwm_ops twl6030_pwmled_ops = {
.enable = twl6030_pwmled_enable,
.disable = twl6030_pwmled_disable,
.config = twl6030_pwmled_config,
.request = twl6030_pwmled_request,
.free = twl6030_pwmled_free,
.owner = THIS_MODULE,
};
static int twl_pwmled_probe(struct platform_device *pdev)
{
struct twl_pwmled_chip *twl;
int ret;
twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL);
if (!twl)
return -ENOMEM;
if (twl_class_is_4030()) {
twl->chip.ops = &twl4030_pwmled_ops;
twl->chip.npwm = 2;
} else {
twl->chip.ops = &twl6030_pwmled_ops;
twl->chip.npwm = 1;
}
twl->chip.dev = &pdev->dev;
twl->chip.base = -1;
twl->chip.can_sleep = true;
mutex_init(&twl->mutex);
ret = pwmchip_add(&twl->chip);
if (ret < 0)
return ret;
platform_set_drvdata(pdev, twl);
return 0;
}
static int twl_pwmled_remove(struct platform_device *pdev)
{
struct twl_pwmled_chip *twl = platform_get_drvdata(pdev);
return pwmchip_remove(&twl->chip);
}
#ifdef CONFIG_OF
static const struct of_device_id twl_pwmled_of_match[] = {
{ .compatible = "ti,twl4030-pwmled" },
{ .compatible = "ti,twl6030-pwmled" },
{ },
};
MODULE_DEVICE_TABLE(of, twl_pwmled_of_match);
#endif
static struct platform_driver twl_pwmled_driver = {
.driver = {
.name = "twl-pwmled",
.of_match_table = of_match_ptr(twl_pwmled_of_match),
},
.probe = twl_pwmled_probe,
.remove = twl_pwmled_remove,
};
module_platform_driver(twl_pwmled_driver);
MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
MODULE_DESCRIPTION("PWM driver for TWL4030 and TWL6030 LED outputs");
MODULE_ALIAS("platform:twl-pwmled");
MODULE_LICENSE("GPL");

359
drivers/pwm/pwm-twl.c Normal file
View File

@@ -0,0 +1,359 @@
/*
* Driver for TWL4030/6030 Generic Pulse Width Modulator
*
* Copyright (C) 2012 Texas Instruments
* Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* 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. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/i2c/twl.h>
#include <linux/slab.h>
/*
* This driver handles the PWMs of TWL4030 and TWL6030.
* The TRM names for the PWMs on TWL4030 are: PWM0, PWM1
* TWL6030 also have two PWMs named in the TRM as PWM1, PWM2
*/
#define TWL_PWM_MAX 0x7f
/* Registers, bits and macro for TWL4030 */
#define TWL4030_GPBR1_REG 0x0c
#define TWL4030_PMBR1_REG 0x0d
/* GPBR1 register bits */
#define TWL4030_PWMXCLK_ENABLE (1 << 0)
#define TWL4030_PWMX_ENABLE (1 << 2)
#define TWL4030_PWMX_BITS (TWL4030_PWMX_ENABLE | TWL4030_PWMXCLK_ENABLE)
#define TWL4030_PWM_TOGGLE(pwm, x) ((x) << (pwm))
/* PMBR1 register bits */
#define TWL4030_GPIO6_PWM0_MUTE_MASK (0x03 << 2)
#define TWL4030_GPIO6_PWM0_MUTE_PWM0 (0x01 << 2)
#define TWL4030_GPIO7_VIBRASYNC_PWM1_MASK (0x03 << 4)
#define TWL4030_GPIO7_VIBRASYNC_PWM1_PWM1 (0x03 << 4)
/* Register, bits and macro for TWL6030 */
#define TWL6030_TOGGLE3_REG 0x92
#define TWL6030_PWMXR (1 << 0)
#define TWL6030_PWMXS (1 << 1)
#define TWL6030_PWMXEN (1 << 2)
#define TWL6030_PWM_TOGGLE(pwm, x) ((x) << (pwm * 3))
struct twl_pwm_chip {
struct pwm_chip chip;
struct mutex mutex;
u8 twl6030_toggle3;
u8 twl4030_pwm_mux;
};
static inline struct twl_pwm_chip *to_twl(struct pwm_chip *chip)
{
return container_of(chip, struct twl_pwm_chip, chip);
}
static int twl_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
int duty_cycle = DIV_ROUND_UP(duty_ns * TWL_PWM_MAX, period_ns) + 1;
u8 pwm_config[2] = { 1, 0 };
int base, ret;
/*
* To configure the duty period:
* On-cycle is set to 1 (the minimum allowed value)
* The off time of 0 is not configurable, so the mapping is:
* 0 -> off cycle = 2,
* 1 -> off cycle = 2,
* 2 -> off cycle = 3,
* 126 - > off cycle 127,
* 127 - > off cycle 1
* When on cycle == off cycle the PWM will be always on
*/
if (duty_cycle == 1)
duty_cycle = 2;
else if (duty_cycle > TWL_PWM_MAX)
duty_cycle = 1;
base = pwm->hwpwm * 3;
pwm_config[1] = duty_cycle;
ret = twl_i2c_write(TWL_MODULE_PWM, pwm_config, base, 2);
if (ret < 0)
dev_err(chip->dev, "%s: Failed to configure PWM\n", pwm->label);
return ret;
}
static int twl4030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct twl_pwm_chip *twl = to_twl(chip);
int ret;
u8 val;
mutex_lock(&twl->mutex);
ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_GPBR1_REG);
if (ret < 0) {
dev_err(chip->dev, "%s: Failed to read GPBR1\n", pwm->label);
goto out;
}
val |= TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMXCLK_ENABLE);
ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
if (ret < 0)
dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
val |= TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_ENABLE);
ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
if (ret < 0)
dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
out:
mutex_unlock(&twl->mutex);
return ret;
}
static void twl4030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct twl_pwm_chip *twl = to_twl(chip);
int ret;
u8 val;
mutex_lock(&twl->mutex);
ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_GPBR1_REG);
if (ret < 0) {
dev_err(chip->dev, "%s: Failed to read GPBR1\n", pwm->label);
goto out;
}
val &= ~TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_ENABLE);
ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
if (ret < 0)
dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
val &= ~TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMXCLK_ENABLE);
ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG);
if (ret < 0)
dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
out:
mutex_unlock(&twl->mutex);
}
static int twl4030_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct twl_pwm_chip *twl = to_twl(chip);
int ret;
u8 val, mask, bits;
if (pwm->hwpwm == 1) {
mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK;
bits = TWL4030_GPIO7_VIBRASYNC_PWM1_PWM1;
} else {
mask = TWL4030_GPIO6_PWM0_MUTE_MASK;
bits = TWL4030_GPIO6_PWM0_MUTE_PWM0;
}
mutex_lock(&twl->mutex);
ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_PMBR1_REG);
if (ret < 0) {
dev_err(chip->dev, "%s: Failed to read PMBR1\n", pwm->label);
goto out;
}
/* Save the current MUX configuration for the PWM */
twl->twl4030_pwm_mux &= ~mask;
twl->twl4030_pwm_mux |= (val & mask);
/* Select PWM functionality */
val &= ~mask;
val |= bits;
ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_PMBR1_REG);
if (ret < 0)
dev_err(chip->dev, "%s: Failed to request PWM\n", pwm->label);
out:
mutex_unlock(&twl->mutex);
return ret;
}
static void twl4030_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct twl_pwm_chip *twl = to_twl(chip);
int ret;
u8 val, mask;
if (pwm->hwpwm == 1)
mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK;
else
mask = TWL4030_GPIO6_PWM0_MUTE_MASK;
mutex_lock(&twl->mutex);
ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_PMBR1_REG);
if (ret < 0) {
dev_err(chip->dev, "%s: Failed to read PMBR1\n", pwm->label);
goto out;
}
/* Restore the MUX configuration for the PWM */
val &= ~mask;
val |= (twl->twl4030_pwm_mux & mask);
ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_PMBR1_REG);
if (ret < 0)
dev_err(chip->dev, "%s: Failed to free PWM\n", pwm->label);
out:
mutex_unlock(&twl->mutex);
}
static int twl6030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct twl_pwm_chip *twl = to_twl(chip);
int ret;
u8 val;
mutex_lock(&twl->mutex);
val = twl->twl6030_toggle3;
val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN);
val &= ~TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXR);
ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG);
if (ret < 0) {
dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label);
goto out;
}
twl->twl6030_toggle3 = val;
out:
mutex_unlock(&twl->mutex);
return ret;
}
static void twl6030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct twl_pwm_chip *twl = to_twl(chip);
int ret;
u8 val;
mutex_lock(&twl->mutex);
val = twl->twl6030_toggle3;
val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXR);
val &= ~TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN);
ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG);
if (ret < 0) {
dev_err(chip->dev, "%s: Failed to read TOGGLE3\n", pwm->label);
goto out;
}
val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN);
ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG);
if (ret < 0) {
dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label);
goto out;
}
twl->twl6030_toggle3 = val;
out:
mutex_unlock(&twl->mutex);
}
static const struct pwm_ops twl4030_pwm_ops = {
.config = twl_pwm_config,
.enable = twl4030_pwm_enable,
.disable = twl4030_pwm_disable,
.request = twl4030_pwm_request,
.free = twl4030_pwm_free,
.owner = THIS_MODULE,
};
static const struct pwm_ops twl6030_pwm_ops = {
.config = twl_pwm_config,
.enable = twl6030_pwm_enable,
.disable = twl6030_pwm_disable,
.owner = THIS_MODULE,
};
static int twl_pwm_probe(struct platform_device *pdev)
{
struct twl_pwm_chip *twl;
int ret;
twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL);
if (!twl)
return -ENOMEM;
if (twl_class_is_4030())
twl->chip.ops = &twl4030_pwm_ops;
else
twl->chip.ops = &twl6030_pwm_ops;
twl->chip.dev = &pdev->dev;
twl->chip.base = -1;
twl->chip.npwm = 2;
twl->chip.can_sleep = true;
mutex_init(&twl->mutex);
ret = pwmchip_add(&twl->chip);
if (ret < 0)
return ret;
platform_set_drvdata(pdev, twl);
return 0;
}
static int twl_pwm_remove(struct platform_device *pdev)
{
struct twl_pwm_chip *twl = platform_get_drvdata(pdev);
return pwmchip_remove(&twl->chip);
}
#ifdef CONFIG_OF
static const struct of_device_id twl_pwm_of_match[] = {
{ .compatible = "ti,twl4030-pwm" },
{ .compatible = "ti,twl6030-pwm" },
{ },
};
MODULE_DEVICE_TABLE(of, twl_pwm_of_match);
#endif
static struct platform_driver twl_pwm_driver = {
.driver = {
.name = "twl-pwm",
.of_match_table = of_match_ptr(twl_pwm_of_match),
},
.probe = twl_pwm_probe,
.remove = twl_pwm_remove,
};
module_platform_driver(twl_pwm_driver);
MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
MODULE_DESCRIPTION("PWM driver for TWL4030 and TWL6030");
MODULE_ALIAS("platform:twl-pwm");
MODULE_LICENSE("GPL");

279
drivers/pwm/pwm-vt8500.c Normal file
View File

@@ -0,0 +1,279 @@
/*
* drivers/pwm/pwm-vt8500.c
*
* Copyright (C) 2012 Tony Prisk <linux@prisktech.co.nz>
* Copyright (C) 2010 Alexey Charkov <alchark@gmail.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/pwm.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <asm/div64.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
/*
* SoC architecture allocates register space for 4 PWMs but only
* 2 are currently implemented.
*/
#define VT8500_NR_PWMS 2
#define REG_CTRL(pwm) (((pwm) << 4) + 0x00)
#define REG_SCALAR(pwm) (((pwm) << 4) + 0x04)
#define REG_PERIOD(pwm) (((pwm) << 4) + 0x08)
#define REG_DUTY(pwm) (((pwm) << 4) + 0x0C)
#define REG_STATUS 0x40
#define CTRL_ENABLE BIT(0)
#define CTRL_INVERT BIT(1)
#define CTRL_AUTOLOAD BIT(2)
#define CTRL_STOP_IMM BIT(3)
#define CTRL_LOAD_PRESCALE BIT(4)
#define CTRL_LOAD_PERIOD BIT(5)
#define STATUS_CTRL_UPDATE BIT(0)
#define STATUS_SCALAR_UPDATE BIT(1)
#define STATUS_PERIOD_UPDATE BIT(2)
#define STATUS_DUTY_UPDATE BIT(3)
#define STATUS_ALL_UPDATE 0x0F
struct vt8500_chip {
struct pwm_chip chip;
void __iomem *base;
struct clk *clk;
};
#define to_vt8500_chip(chip) container_of(chip, struct vt8500_chip, chip)
#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
static inline void pwm_busy_wait(struct vt8500_chip *vt8500, int nr, u8 bitmask)
{
int loops = msecs_to_loops(10);
u32 mask = bitmask << (nr << 8);
while ((readl(vt8500->base + REG_STATUS) & mask) && --loops)
cpu_relax();
if (unlikely(!loops))
dev_warn(vt8500->chip.dev, "Waiting for status bits 0x%x to clear timed out\n",
mask);
}
static int vt8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct vt8500_chip *vt8500 = to_vt8500_chip(chip);
unsigned long long c;
unsigned long period_cycles, prescale, pv, dc;
int err;
u32 val;
err = clk_enable(vt8500->clk);
if (err < 0) {
dev_err(chip->dev, "failed to enable clock\n");
return err;
}
c = clk_get_rate(vt8500->clk);
c = c * period_ns;
do_div(c, 1000000000);
period_cycles = c;
if (period_cycles < 1)
period_cycles = 1;
prescale = (period_cycles - 1) / 4096;
pv = period_cycles / (prescale + 1) - 1;
if (pv > 4095)
pv = 4095;
if (prescale > 1023) {
clk_disable(vt8500->clk);
return -EINVAL;
}
c = (unsigned long long)pv * duty_ns;
do_div(c, period_ns);
dc = c;
writel(prescale, vt8500->base + REG_SCALAR(pwm->hwpwm));
pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_SCALAR_UPDATE);
writel(pv, vt8500->base + REG_PERIOD(pwm->hwpwm));
pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_PERIOD_UPDATE);
writel(dc, vt8500->base + REG_DUTY(pwm->hwpwm));
pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_DUTY_UPDATE);
val = readl(vt8500->base + REG_CTRL(pwm->hwpwm));
val |= CTRL_AUTOLOAD;
writel(val, vt8500->base + REG_CTRL(pwm->hwpwm));
pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_CTRL_UPDATE);
clk_disable(vt8500->clk);
return 0;
}
static int vt8500_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct vt8500_chip *vt8500 = to_vt8500_chip(chip);
int err;
u32 val;
err = clk_enable(vt8500->clk);
if (err < 0) {
dev_err(chip->dev, "failed to enable clock\n");
return err;
}
val = readl(vt8500->base + REG_CTRL(pwm->hwpwm));
val |= CTRL_ENABLE;
writel(val, vt8500->base + REG_CTRL(pwm->hwpwm));
pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_CTRL_UPDATE);
return 0;
}
static void vt8500_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct vt8500_chip *vt8500 = to_vt8500_chip(chip);
u32 val;
val = readl(vt8500->base + REG_CTRL(pwm->hwpwm));
val &= ~CTRL_ENABLE;
writel(val, vt8500->base + REG_CTRL(pwm->hwpwm));
pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_CTRL_UPDATE);
clk_disable(vt8500->clk);
}
static int vt8500_pwm_set_polarity(struct pwm_chip *chip,
struct pwm_device *pwm,
enum pwm_polarity polarity)
{
struct vt8500_chip *vt8500 = to_vt8500_chip(chip);
u32 val;
val = readl(vt8500->base + REG_CTRL(pwm->hwpwm));
if (polarity == PWM_POLARITY_INVERSED)
val |= CTRL_INVERT;
else
val &= ~CTRL_INVERT;
writel(val, vt8500->base + REG_CTRL(pwm->hwpwm));
pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_CTRL_UPDATE);
return 0;
}
static struct pwm_ops vt8500_pwm_ops = {
.enable = vt8500_pwm_enable,
.disable = vt8500_pwm_disable,
.config = vt8500_pwm_config,
.set_polarity = vt8500_pwm_set_polarity,
.owner = THIS_MODULE,
};
static const struct of_device_id vt8500_pwm_dt_ids[] = {
{ .compatible = "via,vt8500-pwm", },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, vt8500_pwm_dt_ids);
static int vt8500_pwm_probe(struct platform_device *pdev)
{
struct vt8500_chip *chip;
struct resource *r;
struct device_node *np = pdev->dev.of_node;
int ret;
if (!np) {
dev_err(&pdev->dev, "invalid devicetree node\n");
return -EINVAL;
}
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
if (chip == NULL) {
dev_err(&pdev->dev, "failed to allocate memory\n");
return -ENOMEM;
}
chip->chip.dev = &pdev->dev;
chip->chip.ops = &vt8500_pwm_ops;
chip->chip.of_xlate = of_pwm_xlate_with_flags;
chip->chip.of_pwm_n_cells = 3;
chip->chip.base = -1;
chip->chip.npwm = VT8500_NR_PWMS;
chip->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(chip->clk)) {
dev_err(&pdev->dev, "clock source not specified\n");
return PTR_ERR(chip->clk);
}
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
chip->base = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(chip->base))
return PTR_ERR(chip->base);
ret = clk_prepare(chip->clk);
if (ret < 0) {
dev_err(&pdev->dev, "failed to prepare clock\n");
return ret;
}
ret = pwmchip_add(&chip->chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add PWM chip\n");
return ret;
}
platform_set_drvdata(pdev, chip);
return ret;
}
static int vt8500_pwm_remove(struct platform_device *pdev)
{
struct vt8500_chip *chip;
chip = platform_get_drvdata(pdev);
if (chip == NULL)
return -ENODEV;
clk_unprepare(chip->clk);
return pwmchip_remove(&chip->chip);
}
static struct platform_driver vt8500_pwm_driver = {
.probe = vt8500_pwm_probe,
.remove = vt8500_pwm_remove,
.driver = {
.name = "vt8500-pwm",
.owner = THIS_MODULE,
.of_match_table = vt8500_pwm_dt_ids,
},
};
module_platform_driver(vt8500_pwm_driver);
MODULE_DESCRIPTION("VT8500 PWM Driver");
MODULE_AUTHOR("Tony Prisk <linux@prisktech.co.nz>");
MODULE_LICENSE("GPL v2");