Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

332
drivers/pwm/Kconfig Normal file
View file

@ -0,0 +1,332 @@
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_SYSFS
bool
default y if SYSFS
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
tristate "Atmel PWM support"
depends on ARCH_AT91 || AVR32
help
Generic PWM framework driver for Atmel SoC.
To compile this driver as a module, choose M here: the module
will be called pwm-atmel.
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_BCM_KONA
tristate "Kona PWM support"
depends on ARCH_BCM_MOBILE
help
Generic PWM framework driver for Broadcom Kona PWM block.
To compile this driver as a module, choose M here: the module
will be called pwm-bcm-kona.
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_CLPS711X
tristate "CLPS711X PWM support"
depends on ARCH_CLPS711X || COMPILE_TEST
depends on HAS_IOMEM
help
Generic PWM framework driver for Cirrus Logic CLPS711X.
To compile this driver as a module, choose M here: the module
will be called pwm-clps711x.
config PWM_EP93XX
tristate "Cirrus Logic EP93xx PWM support"
depends on ARCH_EP93XX
help
Generic PWM framework driver for Cirrus Logic EP93xx.
To compile this driver as a module, choose M here: the module
will be called pwm-ep93xx.
config PWM_FSL_FTM
tristate "Freescale FlexTimer Module (FTM) PWM support"
depends on OF
select REGMAP_MMIO
help
Generic FTM PWM framework driver for Freescale VF610 and
Layerscape LS-1 SoCs.
To compile this driver as a module, choose M here: the module
will be called pwm-fsl-ftm.
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_LP3943
tristate "TI/National Semiconductor LP3943 PWM support"
depends on MFD_LP3943
help
Generic PWM framework driver for LP3943 which supports two PWM
channels.
To compile this driver as a module, choose M here: the module
will be called pwm-lp3943.
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_LPSS
tristate "Intel LPSS PWM support"
depends on X86
help
Generic PWM framework driver for Intel Low Power Subsystem PWM
controller.
To compile this driver as a module, choose M here: the module
will be called pwm-lpss.
config PWM_LPSS_PCI
tristate "Intel LPSS PWM PCI driver"
depends on PWM_LPSS && PCI
help
The PCI driver for Intel Low Power Subsystem PWM controller.
To compile this driver as a module, choose M here: the module
will be called pwm-lpss-pci.
config PWM_LPSS_PLATFORM
tristate "Intel LPSS PWM platform driver"
depends on PWM_LPSS && ACPI
help
The platform driver for Intel Low Power Subsystem PWM controller.
To compile this driver as a module, choose M here: the module
will be called pwm-lpss-platform.
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_PCA9685
tristate "NXP PCA9685 PWM driver"
depends on OF && I2C
select REGMAP_I2C
help
Generic PWM framework driver for NXP PCA9685 LED controller.
To compile this driver as a module, choose M here: the module
will be called pwm-pca9685.
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_RENESAS_TPU
tristate "Renesas TPU PWM support"
depends on ARCH_SHMOBILE || COMPILE_TEST
depends on HAS_IOMEM
help
This driver exposes the Timer Pulse Unit (TPU) PWM controller found
in Renesas chips through the PWM API.
To compile this driver as a module, choose M here: the module
will be called pwm-renesas-tpu.
config PWM_ROCKCHIP
tristate "Rockchip PWM support"
depends on ARCH_ROCKCHIP
help
Generic PWM framework driver for the PWM controller found on
Rockchip SoCs.
config PWM_SAMSUNG
tristate "Samsung PWM support"
depends on ARCH_EXYNOS
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_STI
tristate "STiH4xx PWM support"
depends on ARCH_STI
depends on OF
help
Generic PWM framework driver for STiH4xx SoCs.
To compile this driver as a module, choose M here: the module
will be called pwm-sti.
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

33
drivers/pwm/Makefile Normal file
View file

@ -0,0 +1,33 @@
obj-$(CONFIG_PWM) += core.o
obj-$(CONFIG_PWM_SYSFS) += sysfs.o
obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o
obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o
obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o
obj-$(CONFIG_PWM_BCM_KONA) += pwm-bcm-kona.o
obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o
obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o
obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o
obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o
obj-$(CONFIG_PWM_IMX) += pwm-imx.o
obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o
obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o
obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o
obj-$(CONFIG_PWM_LPSS) += pwm-lpss.o
obj-$(CONFIG_PWM_LPSS_PCI) += pwm-lpss-pci.o
obj-$(CONFIG_PWM_LPSS_PLATFORM) += pwm-lpss-platform.o
obj-$(CONFIG_PWM_MXS) += pwm-mxs.o
obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o
obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o
obj-$(CONFIG_PWM_PXA) += pwm-pxa.o
obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o
obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o
obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o
obj-$(CONFIG_PWM_STI) += pwm-sti.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

895
drivers/pwm/core.c Normal file
View file

@ -0,0 +1,895 @@
/*
* 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>
#include <dt-bindings/pwm/pwm.h>
#define MAX_PWMS 1024
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_POLARITY_INVERTED)
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 || !chip->npwm)
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);
pwmchip_sysfs_export(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);
pwmchip_sysfs_unexport(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)
{
int err;
if (!pwm || duty_ns < 0 || period_ns <= 0 || duty_ns > period_ns)
return -EINVAL;
err = pwm->chip->ops->config(pwm->chip, pwm, duty_ns, period_ns);
if (err)
return err;
pwm->duty_cycle = duty_ns;
pwm->period = period_ns;
return 0;
}
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)
{
int err;
if (!pwm || !pwm->chip->ops)
return -EINVAL;
if (!pwm->chip->ops->set_polarity)
return -ENOSYS;
if (test_bit(PWMF_ENABLED, &pwm->flags))
return -EBUSY;
err = pwm->chip->ops->set_polarity(pwm->chip, pwm, polarity);
if (err)
return err;
pwm->polarity = polarity;
return 0;
}
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 best = 0;
struct pwm_lookup *p, *chosen = NULL;
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) {
chosen = p;
if (match != 3)
best = match;
else
break;
}
}
if (!chosen)
goto out;
chip = pwmchip_find_by_name(chosen->provider);
if (!chip)
goto out;
pwm = pwm_request_from_chip(chip, chosen->index, con_id ?: dev_id);
if (IS_ERR(pwm))
goto out;
pwm_set_period(pwm, chosen->period);
pwm_set_polarity(pwm, chosen->polarity);
out:
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_puts(s, " requested");
if (test_bit(PWMF_ENABLED, &pwm->flags))
seq_puts(s, " enabled");
seq_puts(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 */

144
drivers/pwm/pwm-ab8500.c Normal file
View file

@ -0,0 +1,144 @@
/*
* 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
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), 1 << (chip->base - 1));
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), 0);
if (ret < 0)
dev_err(chip->dev, "%s: Failed to disable PWM, Error %d\n",
pwm->label, ret);
}
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)
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");

449
drivers/pwm/pwm-atmel-tcb.c Normal file
View file

@ -0,0 +1,449 @@
/*
* 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_prepare_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_unprepare(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;
}
}
cmr |= (tcbpwm->div & ATMEL_TC_TCCLKS);
__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 << tc->tcb_config->counter_width;
/* 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);
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",
.owner = THIS_MODULE,
.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");

399
drivers/pwm/pwm-atmel.c Normal file
View file

@ -0,0 +1,399 @@
/*
* Driver for Atmel Pulse Width Modulation Controller
*
* Copyright (C) 2013 Atmel Corporation
* Bo Shen <voice.shen@atmel.com>
*
* Licensed under GPLv2.
*/
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>
/* The following is global registers for PWM controller */
#define PWM_ENA 0x04
#define PWM_DIS 0x08
#define PWM_SR 0x0C
/* Bit field in SR */
#define PWM_SR_ALL_CH_ON 0x0F
/* The following register is PWM channel related registers */
#define PWM_CH_REG_OFFSET 0x200
#define PWM_CH_REG_SIZE 0x20
#define PWM_CMR 0x0
/* Bit field in CMR */
#define PWM_CMR_CPOL (1 << 9)
#define PWM_CMR_UPD_CDTY (1 << 10)
#define PWM_CMR_CPRE_MSK 0xF
/* The following registers for PWM v1 */
#define PWMV1_CDTY 0x04
#define PWMV1_CPRD 0x08
#define PWMV1_CUPD 0x10
/* The following registers for PWM v2 */
#define PWMV2_CDTY 0x04
#define PWMV2_CDTYUPD 0x08
#define PWMV2_CPRD 0x0C
#define PWMV2_CPRDUPD 0x10
/*
* Max value for duty and period
*
* Although the duty and period register is 32 bit,
* however only the LSB 16 bits are significant.
*/
#define PWM_MAX_DTY 0xFFFF
#define PWM_MAX_PRD 0xFFFF
#define PRD_MAX_PRES 10
struct atmel_pwm_chip {
struct pwm_chip chip;
struct clk *clk;
void __iomem *base;
void (*config)(struct pwm_chip *chip, struct pwm_device *pwm,
unsigned long dty, unsigned long prd);
};
static inline struct atmel_pwm_chip *to_atmel_pwm_chip(struct pwm_chip *chip)
{
return container_of(chip, struct atmel_pwm_chip, chip);
}
static inline u32 atmel_pwm_readl(struct atmel_pwm_chip *chip,
unsigned long offset)
{
return readl_relaxed(chip->base + offset);
}
static inline void atmel_pwm_writel(struct atmel_pwm_chip *chip,
unsigned long offset, unsigned long val)
{
writel_relaxed(val, chip->base + offset);
}
static inline u32 atmel_pwm_ch_readl(struct atmel_pwm_chip *chip,
unsigned int ch, unsigned long offset)
{
unsigned long base = PWM_CH_REG_OFFSET + ch * PWM_CH_REG_SIZE;
return readl_relaxed(chip->base + base + offset);
}
static inline void atmel_pwm_ch_writel(struct atmel_pwm_chip *chip,
unsigned int ch, unsigned long offset,
unsigned long val)
{
unsigned long base = PWM_CH_REG_OFFSET + ch * PWM_CH_REG_SIZE;
writel_relaxed(val, chip->base + base + offset);
}
static int atmel_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
unsigned long prd, dty;
unsigned long long div;
unsigned int pres = 0;
u32 val;
int ret;
if (test_bit(PWMF_ENABLED, &pwm->flags) && (period_ns != pwm->period)) {
dev_err(chip->dev, "cannot change PWM period while enabled\n");
return -EBUSY;
}
/* Calculate the period cycles and prescale value */
div = (unsigned long long)clk_get_rate(atmel_pwm->clk) * period_ns;
do_div(div, NSEC_PER_SEC);
while (div > PWM_MAX_PRD) {
div >>= 1;
pres++;
}
if (pres > PRD_MAX_PRES) {
dev_err(chip->dev, "pres exceeds the maximum value\n");
return -EINVAL;
}
/* Calculate the duty cycles */
prd = div;
div *= duty_ns;
do_div(div, period_ns);
dty = prd - div;
ret = clk_enable(atmel_pwm->clk);
if (ret) {
dev_err(chip->dev, "failed to enable PWM clock\n");
return ret;
}
/* It is necessary to preserve CPOL, inside CMR */
val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR);
val = (val & ~PWM_CMR_CPRE_MSK) | (pres & PWM_CMR_CPRE_MSK);
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
atmel_pwm->config(chip, pwm, dty, prd);
clk_disable(atmel_pwm->clk);
return ret;
}
static void atmel_pwm_config_v1(struct pwm_chip *chip, struct pwm_device *pwm,
unsigned long dty, unsigned long prd)
{
struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
unsigned int val;
if (test_bit(PWMF_ENABLED, &pwm->flags)) {
/*
* If the PWM channel is enabled, using the update register,
* it needs to set bit 10 of CMR to 0
*/
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CUPD, dty);
val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR);
val &= ~PWM_CMR_UPD_CDTY;
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
} else {
/*
* If the PWM channel is disabled, write value to duty and
* period registers directly.
*/
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CDTY, dty);
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CPRD, prd);
}
}
static void atmel_pwm_config_v2(struct pwm_chip *chip, struct pwm_device *pwm,
unsigned long dty, unsigned long prd)
{
struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
if (test_bit(PWMF_ENABLED, &pwm->flags)) {
/*
* If the PWM channel is enabled, using the duty update register
* to update the value.
*/
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV2_CDTYUPD, dty);
} else {
/*
* If the PWM channel is disabled, write value to duty and
* period registers directly.
*/
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV2_CDTY, dty);
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV2_CPRD, prd);
}
}
static int atmel_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
enum pwm_polarity polarity)
{
struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
u32 val;
int ret;
val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR);
if (polarity == PWM_POLARITY_NORMAL)
val &= ~PWM_CMR_CPOL;
else
val |= PWM_CMR_CPOL;
ret = clk_enable(atmel_pwm->clk);
if (ret) {
dev_err(chip->dev, "failed to enable PWM clock\n");
return ret;
}
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
clk_disable(atmel_pwm->clk);
return 0;
}
static int atmel_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
int ret;
ret = clk_enable(atmel_pwm->clk);
if (ret) {
dev_err(chip->dev, "failed to enable PWM clock\n");
return ret;
}
atmel_pwm_writel(atmel_pwm, PWM_ENA, 1 << pwm->hwpwm);
return 0;
}
static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
atmel_pwm_writel(atmel_pwm, PWM_DIS, 1 << pwm->hwpwm);
clk_disable(atmel_pwm->clk);
}
static const struct pwm_ops atmel_pwm_ops = {
.config = atmel_pwm_config,
.set_polarity = atmel_pwm_set_polarity,
.enable = atmel_pwm_enable,
.disable = atmel_pwm_disable,
.owner = THIS_MODULE,
};
struct atmel_pwm_data {
void (*config)(struct pwm_chip *chip, struct pwm_device *pwm,
unsigned long dty, unsigned long prd);
};
static const struct atmel_pwm_data atmel_pwm_data_v1 = {
.config = atmel_pwm_config_v1,
};
static const struct atmel_pwm_data atmel_pwm_data_v2 = {
.config = atmel_pwm_config_v2,
};
static const struct platform_device_id atmel_pwm_devtypes[] = {
{
.name = "at91sam9rl-pwm",
.driver_data = (kernel_ulong_t)&atmel_pwm_data_v1,
}, {
.name = "sama5d3-pwm",
.driver_data = (kernel_ulong_t)&atmel_pwm_data_v2,
}, {
/* sentinel */
},
};
MODULE_DEVICE_TABLE(platform, atmel_pwm_devtypes);
static const struct of_device_id atmel_pwm_dt_ids[] = {
{
.compatible = "atmel,at91sam9rl-pwm",
.data = &atmel_pwm_data_v1,
}, {
.compatible = "atmel,sama5d3-pwm",
.data = &atmel_pwm_data_v2,
}, {
/* sentinel */
},
};
MODULE_DEVICE_TABLE(of, atmel_pwm_dt_ids);
static inline const struct atmel_pwm_data *
atmel_pwm_get_driver_data(struct platform_device *pdev)
{
if (pdev->dev.of_node) {
const struct of_device_id *match;
match = of_match_device(atmel_pwm_dt_ids, &pdev->dev);
if (!match)
return NULL;
return match->data;
} else {
const struct platform_device_id *id;
id = platform_get_device_id(pdev);
return (struct atmel_pwm_data *)id->driver_data;
}
}
static int atmel_pwm_probe(struct platform_device *pdev)
{
const struct atmel_pwm_data *data;
struct atmel_pwm_chip *atmel_pwm;
struct resource *res;
int ret;
data = atmel_pwm_get_driver_data(pdev);
if (!data)
return -ENODEV;
atmel_pwm = devm_kzalloc(&pdev->dev, sizeof(*atmel_pwm), GFP_KERNEL);
if (!atmel_pwm)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
atmel_pwm->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(atmel_pwm->base))
return PTR_ERR(atmel_pwm->base);
atmel_pwm->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(atmel_pwm->clk))
return PTR_ERR(atmel_pwm->clk);
ret = clk_prepare(atmel_pwm->clk);
if (ret) {
dev_err(&pdev->dev, "failed to prepare PWM clock\n");
return ret;
}
atmel_pwm->chip.dev = &pdev->dev;
atmel_pwm->chip.ops = &atmel_pwm_ops;
if (pdev->dev.of_node) {
atmel_pwm->chip.of_xlate = of_pwm_xlate_with_flags;
atmel_pwm->chip.of_pwm_n_cells = 3;
}
atmel_pwm->chip.base = -1;
atmel_pwm->chip.npwm = 4;
atmel_pwm->chip.can_sleep = true;
atmel_pwm->config = data->config;
ret = pwmchip_add(&atmel_pwm->chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add PWM chip %d\n", ret);
goto unprepare_clk;
}
platform_set_drvdata(pdev, atmel_pwm);
return ret;
unprepare_clk:
clk_unprepare(atmel_pwm->clk);
return ret;
}
static int atmel_pwm_remove(struct platform_device *pdev)
{
struct atmel_pwm_chip *atmel_pwm = platform_get_drvdata(pdev);
clk_unprepare(atmel_pwm->clk);
return pwmchip_remove(&atmel_pwm->chip);
}
static struct platform_driver atmel_pwm_driver = {
.driver = {
.name = "atmel-pwm",
.of_match_table = of_match_ptr(atmel_pwm_dt_ids),
},
.id_table = atmel_pwm_devtypes,
.probe = atmel_pwm_probe,
.remove = atmel_pwm_remove,
};
module_platform_driver(atmel_pwm_driver);
MODULE_ALIAS("platform:atmel-pwm");
MODULE_AUTHOR("Bo Shen <voice.shen@atmel.com>");
MODULE_DESCRIPTION("Atmel PWM driver");
MODULE_LICENSE("GPL v2");

318
drivers/pwm/pwm-bcm-kona.c Normal file
View file

@ -0,0 +1,318 @@
/*
* Copyright (C) 2014 Broadcom Corporation
*
* 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.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/ioport.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>
/*
* The Kona PWM has some unusual characteristics. Here are the main points.
*
* 1) There is no disable bit and the hardware docs advise programming a zero
* duty to achieve output equivalent to that of a normal disable operation.
*
* 2) Changes to prescale, duty, period, and polarity do not take effect until
* a subsequent rising edge of the trigger bit.
*
* 3) If the smooth bit and trigger bit are both low, the output is a constant
* high signal. Otherwise, the earlier waveform continues to be output.
*
* 4) If the smooth bit is set on the rising edge of the trigger bit, output
* will transition to the new settings on a period boundary (which could be
* seconds away). If the smooth bit is clear, new settings will be applied
* as soon as possible (the hardware always has a 400ns delay).
*
* 5) When the external clock that feeds the PWM is disabled, output is pegged
* high or low depending on its state at that exact instant.
*/
#define PWM_CONTROL_OFFSET (0x00000000)
#define PWM_CONTROL_SMOOTH_SHIFT(chan) (24 + (chan))
#define PWM_CONTROL_TYPE_SHIFT(chan) (16 + (chan))
#define PWM_CONTROL_POLARITY_SHIFT(chan) (8 + (chan))
#define PWM_CONTROL_TRIGGER_SHIFT(chan) (chan)
#define PRESCALE_OFFSET (0x00000004)
#define PRESCALE_SHIFT(chan) ((chan) << 2)
#define PRESCALE_MASK(chan) (0x7 << PRESCALE_SHIFT(chan))
#define PRESCALE_MIN (0x00000000)
#define PRESCALE_MAX (0x00000007)
#define PERIOD_COUNT_OFFSET(chan) (0x00000008 + ((chan) << 3))
#define PERIOD_COUNT_MIN (0x00000002)
#define PERIOD_COUNT_MAX (0x00ffffff)
#define DUTY_CYCLE_HIGH_OFFSET(chan) (0x0000000c + ((chan) << 3))
#define DUTY_CYCLE_HIGH_MIN (0x00000000)
#define DUTY_CYCLE_HIGH_MAX (0x00ffffff)
struct kona_pwmc {
struct pwm_chip chip;
void __iomem *base;
struct clk *clk;
};
static inline struct kona_pwmc *to_kona_pwmc(struct pwm_chip *_chip)
{
return container_of(_chip, struct kona_pwmc, chip);
}
static void kona_pwmc_apply_settings(struct kona_pwmc *kp, unsigned int chan)
{
unsigned int value = readl(kp->base + PWM_CONTROL_OFFSET);
/* Clear trigger bit but set smooth bit to maintain old output */
value |= 1 << PWM_CONTROL_SMOOTH_SHIFT(chan);
value &= ~(1 << PWM_CONTROL_TRIGGER_SHIFT(chan));
writel(value, kp->base + PWM_CONTROL_OFFSET);
/* Set trigger bit and clear smooth bit to apply new settings */
value &= ~(1 << PWM_CONTROL_SMOOTH_SHIFT(chan));
value |= 1 << PWM_CONTROL_TRIGGER_SHIFT(chan);
writel(value, kp->base + PWM_CONTROL_OFFSET);
}
static int kona_pwmc_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct kona_pwmc *kp = to_kona_pwmc(chip);
u64 val, div, rate;
unsigned long prescale = PRESCALE_MIN, pc, dc;
unsigned int value, chan = pwm->hwpwm;
/*
* Find period count, duty count and prescale to suit duty_ns and
* period_ns. This is done according to formulas described below:
*
* period_ns = 10^9 * (PRESCALE + 1) * PC / PWM_CLK_RATE
* duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
*
* PC = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1))
* DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1))
*/
rate = clk_get_rate(kp->clk);
while (1) {
div = 1000000000;
div *= 1 + prescale;
val = rate * period_ns;
pc = div64_u64(val, div);
val = rate * duty_ns;
dc = div64_u64(val, div);
/* If duty_ns or period_ns are not achievable then return */
if (pc < PERIOD_COUNT_MIN || dc < DUTY_CYCLE_HIGH_MIN)
return -EINVAL;
/* If pc and dc are in bounds, the calculation is done */
if (pc <= PERIOD_COUNT_MAX && dc <= DUTY_CYCLE_HIGH_MAX)
break;
/* Otherwise, increase prescale and recalculate pc and dc */
if (++prescale > PRESCALE_MAX)
return -EINVAL;
}
/* If the PWM channel is enabled, write the settings to the HW */
if (test_bit(PWMF_ENABLED, &pwm->flags)) {
value = readl(kp->base + PRESCALE_OFFSET);
value &= ~PRESCALE_MASK(chan);
value |= prescale << PRESCALE_SHIFT(chan);
writel(value, kp->base + PRESCALE_OFFSET);
writel(pc, kp->base + PERIOD_COUNT_OFFSET(chan));
writel(dc, kp->base + DUTY_CYCLE_HIGH_OFFSET(chan));
kona_pwmc_apply_settings(kp, chan);
}
return 0;
}
static int kona_pwmc_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
enum pwm_polarity polarity)
{
struct kona_pwmc *kp = to_kona_pwmc(chip);
unsigned int chan = pwm->hwpwm;
unsigned int value;
int ret;
ret = clk_prepare_enable(kp->clk);
if (ret < 0) {
dev_err(chip->dev, "failed to enable clock: %d\n", ret);
return ret;
}
value = readl(kp->base + PWM_CONTROL_OFFSET);
if (polarity == PWM_POLARITY_NORMAL)
value |= 1 << PWM_CONTROL_POLARITY_SHIFT(chan);
else
value &= ~(1 << PWM_CONTROL_POLARITY_SHIFT(chan));
writel(value, kp->base + PWM_CONTROL_OFFSET);
kona_pwmc_apply_settings(kp, chan);
/* Wait for waveform to settle before gating off the clock */
ndelay(400);
clk_disable_unprepare(kp->clk);
return 0;
}
static int kona_pwmc_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct kona_pwmc *kp = to_kona_pwmc(chip);
int ret;
ret = clk_prepare_enable(kp->clk);
if (ret < 0) {
dev_err(chip->dev, "failed to enable clock: %d\n", ret);
return ret;
}
ret = kona_pwmc_config(chip, pwm, pwm->duty_cycle, pwm->period);
if (ret < 0) {
clk_disable_unprepare(kp->clk);
return ret;
}
return 0;
}
static void kona_pwmc_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct kona_pwmc *kp = to_kona_pwmc(chip);
unsigned int chan = pwm->hwpwm;
/* Simulate a disable by configuring for zero duty */
writel(0, kp->base + DUTY_CYCLE_HIGH_OFFSET(chan));
kona_pwmc_apply_settings(kp, chan);
/* Wait for waveform to settle before gating off the clock */
ndelay(400);
clk_disable_unprepare(kp->clk);
}
static const struct pwm_ops kona_pwm_ops = {
.config = kona_pwmc_config,
.set_polarity = kona_pwmc_set_polarity,
.enable = kona_pwmc_enable,
.disable = kona_pwmc_disable,
.owner = THIS_MODULE,
};
static int kona_pwmc_probe(struct platform_device *pdev)
{
struct kona_pwmc *kp;
struct resource *res;
unsigned int chan;
unsigned int value = 0;
int ret = 0;
kp = devm_kzalloc(&pdev->dev, sizeof(*kp), GFP_KERNEL);
if (kp == NULL)
return -ENOMEM;
platform_set_drvdata(pdev, kp);
kp->chip.dev = &pdev->dev;
kp->chip.ops = &kona_pwm_ops;
kp->chip.base = -1;
kp->chip.npwm = 6;
kp->chip.of_xlate = of_pwm_xlate_with_flags;
kp->chip.of_pwm_n_cells = 3;
kp->chip.can_sleep = true;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
kp->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(kp->base))
return PTR_ERR(kp->base);
kp->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(kp->clk)) {
dev_err(&pdev->dev, "failed to get clock: %ld\n",
PTR_ERR(kp->clk));
return PTR_ERR(kp->clk);
}
ret = clk_prepare_enable(kp->clk);
if (ret < 0) {
dev_err(&pdev->dev, "failed to enable clock: %d\n", ret);
return ret;
}
/* Set smooth mode, push/pull, and normal polarity for all channels */
for (chan = 0; chan < kp->chip.npwm; chan++) {
value |= (1 << PWM_CONTROL_SMOOTH_SHIFT(chan));
value |= (1 << PWM_CONTROL_TYPE_SHIFT(chan));
value |= (1 << PWM_CONTROL_POLARITY_SHIFT(chan));
}
writel(value, kp->base + PWM_CONTROL_OFFSET);
clk_disable_unprepare(kp->clk);
ret = pwmchip_add(&kp->chip);
if (ret < 0)
dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
return ret;
}
static int kona_pwmc_remove(struct platform_device *pdev)
{
struct kona_pwmc *kp = platform_get_drvdata(pdev);
unsigned int chan;
for (chan = 0; chan < kp->chip.npwm; chan++)
if (test_bit(PWMF_ENABLED, &kp->chip.pwms[chan].flags))
clk_disable_unprepare(kp->clk);
return pwmchip_remove(&kp->chip);
}
static const struct of_device_id bcm_kona_pwmc_dt[] = {
{ .compatible = "brcm,kona-pwm" },
{ },
};
MODULE_DEVICE_TABLE(of, bcm_kona_pwmc_dt);
static struct platform_driver kona_pwmc_driver = {
.driver = {
.name = "bcm-kona-pwm",
.of_match_table = bcm_kona_pwmc_dt,
},
.probe = kona_pwmc_probe,
.remove = kona_pwmc_remove,
};
module_platform_driver(kona_pwmc_driver);
MODULE_AUTHOR("Broadcom Corporation <bcm-kernel-feedback-list@broadcom.com>");
MODULE_AUTHOR("Tim Kryger <tkryger@broadcom.com>");
MODULE_DESCRIPTION("Broadcom Kona PWM driver");
MODULE_LICENSE("GPL v2");

160
drivers/pwm/pwm-bfin.c Normal file
View file

@ -0,0 +1,160 @@
/*
* 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",
.owner = THIS_MODULE,
},
.probe = bfin_pwm_probe,
.remove = bfin_pwm_remove,
};
module_platform_driver(bfin_pwm_driver);
MODULE_LICENSE("GPL");

176
drivers/pwm/pwm-clps711x.c Normal file
View file

@ -0,0 +1,176 @@
/*
* Cirrus Logic CLPS711X PWM driver
*
* Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru>
*
* 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.
*/
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
struct clps711x_chip {
struct pwm_chip chip;
void __iomem *pmpcon;
struct clk *clk;
spinlock_t lock;
};
static inline struct clps711x_chip *to_clps711x_chip(struct pwm_chip *chip)
{
return container_of(chip, struct clps711x_chip, chip);
}
static void clps711x_pwm_update_val(struct clps711x_chip *priv, u32 n, u32 v)
{
/* PWM0 - bits 4..7, PWM1 - bits 8..11 */
u32 shift = (n + 1) * 4;
unsigned long flags;
u32 tmp;
spin_lock_irqsave(&priv->lock, flags);
tmp = readl(priv->pmpcon);
tmp &= ~(0xf << shift);
tmp |= v << shift;
writel(tmp, priv->pmpcon);
spin_unlock_irqrestore(&priv->lock, flags);
}
static unsigned int clps711x_get_duty(struct pwm_device *pwm, unsigned int v)
{
/* Duty cycle 0..15 max */
return DIV_ROUND_CLOSEST(v * 0xf, pwm_get_period(pwm));
}
static int clps711x_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct clps711x_chip *priv = to_clps711x_chip(chip);
unsigned int freq = clk_get_rate(priv->clk);
if (!freq)
return -EINVAL;
/* Store constant period value */
pwm_set_period(pwm, DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq));
return 0;
}
static int clps711x_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct clps711x_chip *priv = to_clps711x_chip(chip);
unsigned int duty;
if (period_ns != pwm_get_period(pwm))
return -EINVAL;
duty = clps711x_get_duty(pwm, duty_ns);
clps711x_pwm_update_val(priv, pwm->hwpwm, duty);
return 0;
}
static int clps711x_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct clps711x_chip *priv = to_clps711x_chip(chip);
unsigned int duty;
duty = clps711x_get_duty(pwm, pwm_get_duty_cycle(pwm));
clps711x_pwm_update_val(priv, pwm->hwpwm, duty);
return 0;
}
static void clps711x_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct clps711x_chip *priv = to_clps711x_chip(chip);
clps711x_pwm_update_val(priv, pwm->hwpwm, 0);
}
static const struct pwm_ops clps711x_pwm_ops = {
.request = clps711x_pwm_request,
.config = clps711x_pwm_config,
.enable = clps711x_pwm_enable,
.disable = clps711x_pwm_disable,
.owner = THIS_MODULE,
};
static struct pwm_device *clps711x_pwm_xlate(struct pwm_chip *chip,
const struct of_phandle_args *args)
{
if (args->args[0] >= chip->npwm)
return ERR_PTR(-EINVAL);
return pwm_request_from_chip(chip, args->args[0], NULL);
}
static int clps711x_pwm_probe(struct platform_device *pdev)
{
struct clps711x_chip *priv;
struct resource *res;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
priv->pmpcon = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(priv->pmpcon))
return PTR_ERR(priv->pmpcon);
priv->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(priv->clk))
return PTR_ERR(priv->clk);
priv->chip.ops = &clps711x_pwm_ops;
priv->chip.dev = &pdev->dev;
priv->chip.base = -1;
priv->chip.npwm = 2;
priv->chip.of_xlate = clps711x_pwm_xlate;
priv->chip.of_pwm_n_cells = 1;
spin_lock_init(&priv->lock);
platform_set_drvdata(pdev, priv);
return pwmchip_add(&priv->chip);
}
static int clps711x_pwm_remove(struct platform_device *pdev)
{
struct clps711x_chip *priv = platform_get_drvdata(pdev);
return pwmchip_remove(&priv->chip);
}
static const struct of_device_id __maybe_unused clps711x_pwm_dt_ids[] = {
{ .compatible = "cirrus,clps711x-pwm", },
{ }
};
MODULE_DEVICE_TABLE(of, clps711x_pwm_dt_ids);
static struct platform_driver clps711x_pwm_driver = {
.driver = {
.name = "clps711x-pwm",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(clps711x_pwm_dt_ids),
},
.probe = clps711x_pwm_probe,
.remove = clps711x_pwm_remove,
};
module_platform_driver(clps711x_pwm_driver);
MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
MODULE_DESCRIPTION("Cirrus Logic CLPS711X PWM driver");
MODULE_LICENSE("GPL");

230
drivers/pwm/pwm-ep93xx.c Normal file
View file

@ -0,0 +1,230 @@
/*
* PWM framework driver for Cirrus Logic EP93xx
*
* Copyright (c) 2009 Matthieu Crapet <mcrapet@gmail.com>
* Copyright (c) 2009, 2013 H Hartley Sweeten <hsweeten@visionengravers.com>
*
* EP9301/02 have only one channel:
* platform device ep93xx-pwm.1 - PWMOUT1 (EGPIO14)
*
* EP9307 has only one channel:
* platform device ep93xx-pwm.0 - PWMOUT
*
* EP9312/15 have two channels:
* platform device ep93xx-pwm.0 - PWMOUT
* platform device ep93xx-pwm.1 - PWMOUT1 (EGPIO14)
*
* 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/slab.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/pwm.h>
#include <asm/div64.h>
#include <mach/platform.h> /* for ep93xx_pwm_{acquire,release}_gpio() */
#define EP93XX_PWMx_TERM_COUNT 0x00
#define EP93XX_PWMx_DUTY_CYCLE 0x04
#define EP93XX_PWMx_ENABLE 0x08
#define EP93XX_PWMx_INVERT 0x0c
struct ep93xx_pwm {
void __iomem *base;
struct clk *clk;
struct pwm_chip chip;
};
static inline struct ep93xx_pwm *to_ep93xx_pwm(struct pwm_chip *chip)
{
return container_of(chip, struct ep93xx_pwm, chip);
}
static int ep93xx_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct platform_device *pdev = to_platform_device(chip->dev);
return ep93xx_pwm_acquire_gpio(pdev);
}
static void ep93xx_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct platform_device *pdev = to_platform_device(chip->dev);
ep93xx_pwm_release_gpio(pdev);
}
static int ep93xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct ep93xx_pwm *ep93xx_pwm = to_ep93xx_pwm(chip);
void __iomem *base = ep93xx_pwm->base;
unsigned long long c;
unsigned long period_cycles;
unsigned long duty_cycles;
unsigned long term;
int ret = 0;
/*
* The clock needs to be enabled to access the PWM registers.
* Configuration can be changed at any time.
*/
if (!test_bit(PWMF_ENABLED, &pwm->flags)) {
ret = clk_enable(ep93xx_pwm->clk);
if (ret)
return ret;
}
c = clk_get_rate(ep93xx_pwm->clk);
c *= period_ns;
do_div(c, 1000000000);
period_cycles = c;
c = period_cycles;
c *= duty_ns;
do_div(c, period_ns);
duty_cycles = c;
if (period_cycles < 0x10000 && duty_cycles < 0x10000) {
term = readw(base + EP93XX_PWMx_TERM_COUNT);
/* Order is important if PWM is running */
if (period_cycles > term) {
writew(period_cycles, base + EP93XX_PWMx_TERM_COUNT);
writew(duty_cycles, base + EP93XX_PWMx_DUTY_CYCLE);
} else {
writew(duty_cycles, base + EP93XX_PWMx_DUTY_CYCLE);
writew(period_cycles, base + EP93XX_PWMx_TERM_COUNT);
}
} else {
ret = -EINVAL;
}
if (!test_bit(PWMF_ENABLED, &pwm->flags))
clk_disable(ep93xx_pwm->clk);
return ret;
}
static int ep93xx_pwm_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
enum pwm_polarity polarity)
{
struct ep93xx_pwm *ep93xx_pwm = to_ep93xx_pwm(chip);
int ret;
/*
* The clock needs to be enabled to access the PWM registers.
* Polarity can only be changed when the PWM is disabled.
*/
ret = clk_enable(ep93xx_pwm->clk);
if (ret)
return ret;
if (polarity == PWM_POLARITY_INVERSED)
writew(0x1, ep93xx_pwm->base + EP93XX_PWMx_INVERT);
else
writew(0x0, ep93xx_pwm->base + EP93XX_PWMx_INVERT);
clk_disable(ep93xx_pwm->clk);
return 0;
}
static int ep93xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct ep93xx_pwm *ep93xx_pwm = to_ep93xx_pwm(chip);
int ret;
ret = clk_enable(ep93xx_pwm->clk);
if (ret)
return ret;
writew(0x1, ep93xx_pwm->base + EP93XX_PWMx_ENABLE);
return 0;
}
static void ep93xx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct ep93xx_pwm *ep93xx_pwm = to_ep93xx_pwm(chip);
writew(0x0, ep93xx_pwm->base + EP93XX_PWMx_ENABLE);
clk_disable(ep93xx_pwm->clk);
}
static const struct pwm_ops ep93xx_pwm_ops = {
.request = ep93xx_pwm_request,
.free = ep93xx_pwm_free,
.config = ep93xx_pwm_config,
.set_polarity = ep93xx_pwm_polarity,
.enable = ep93xx_pwm_enable,
.disable = ep93xx_pwm_disable,
.owner = THIS_MODULE,
};
static int ep93xx_pwm_probe(struct platform_device *pdev)
{
struct ep93xx_pwm *ep93xx_pwm;
struct resource *res;
int ret;
ep93xx_pwm = devm_kzalloc(&pdev->dev, sizeof(*ep93xx_pwm), GFP_KERNEL);
if (!ep93xx_pwm)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
ep93xx_pwm->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(ep93xx_pwm->base))
return PTR_ERR(ep93xx_pwm->base);
ep93xx_pwm->clk = devm_clk_get(&pdev->dev, "pwm_clk");
if (IS_ERR(ep93xx_pwm->clk))
return PTR_ERR(ep93xx_pwm->clk);
ep93xx_pwm->chip.dev = &pdev->dev;
ep93xx_pwm->chip.ops = &ep93xx_pwm_ops;
ep93xx_pwm->chip.base = -1;
ep93xx_pwm->chip.npwm = 1;
ret = pwmchip_add(&ep93xx_pwm->chip);
if (ret < 0)
return ret;
platform_set_drvdata(pdev, ep93xx_pwm);
return 0;
}
static int ep93xx_pwm_remove(struct platform_device *pdev)
{
struct ep93xx_pwm *ep93xx_pwm = platform_get_drvdata(pdev);
return pwmchip_remove(&ep93xx_pwm->chip);
}
static struct platform_driver ep93xx_pwm_driver = {
.driver = {
.name = "ep93xx-pwm",
},
.probe = ep93xx_pwm_probe,
.remove = ep93xx_pwm_remove,
};
module_platform_driver(ep93xx_pwm_driver);
MODULE_DESCRIPTION("Cirrus Logic EP93xx PWM driver");
MODULE_AUTHOR("Matthieu Crapet <mcrapet@gmail.com>");
MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
MODULE_ALIAS("platform:ep93xx-pwm");
MODULE_LICENSE("GPL");

500
drivers/pwm/pwm-fsl-ftm.c Normal file
View file

@ -0,0 +1,500 @@
/*
* Freescale FlexTimer Module (FTM) PWM Driver
*
* Copyright 2012-2013 Freescale Semiconductor, Inc.
*
* 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.
*/
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#define FTM_SC 0x00
#define FTM_SC_CLK_MASK_SHIFT 3
#define FTM_SC_CLK_MASK (3 << FTM_SC_CLK_MASK_SHIFT)
#define FTM_SC_CLK(c) (((c) + 1) << FTM_SC_CLK_MASK_SHIFT)
#define FTM_SC_PS_MASK 0x7
#define FTM_CNT 0x04
#define FTM_MOD 0x08
#define FTM_CSC_BASE 0x0C
#define FTM_CSC_MSB BIT(5)
#define FTM_CSC_MSA BIT(4)
#define FTM_CSC_ELSB BIT(3)
#define FTM_CSC_ELSA BIT(2)
#define FTM_CSC(_channel) (FTM_CSC_BASE + ((_channel) * 8))
#define FTM_CV_BASE 0x10
#define FTM_CV(_channel) (FTM_CV_BASE + ((_channel) * 8))
#define FTM_CNTIN 0x4C
#define FTM_STATUS 0x50
#define FTM_MODE 0x54
#define FTM_MODE_FTMEN BIT(0)
#define FTM_MODE_INIT BIT(2)
#define FTM_MODE_PWMSYNC BIT(3)
#define FTM_SYNC 0x58
#define FTM_OUTINIT 0x5C
#define FTM_OUTMASK 0x60
#define FTM_COMBINE 0x64
#define FTM_DEADTIME 0x68
#define FTM_EXTTRIG 0x6C
#define FTM_POL 0x70
#define FTM_FMS 0x74
#define FTM_FILTER 0x78
#define FTM_FLTCTRL 0x7C
#define FTM_QDCTRL 0x80
#define FTM_CONF 0x84
#define FTM_FLTPOL 0x88
#define FTM_SYNCONF 0x8C
#define FTM_INVCTRL 0x90
#define FTM_SWOCTRL 0x94
#define FTM_PWMLOAD 0x98
enum fsl_pwm_clk {
FSL_PWM_CLK_SYS,
FSL_PWM_CLK_FIX,
FSL_PWM_CLK_EXT,
FSL_PWM_CLK_CNTEN,
FSL_PWM_CLK_MAX
};
struct fsl_pwm_chip {
struct pwm_chip chip;
struct mutex lock;
unsigned int use_count;
unsigned int cnt_select;
unsigned int clk_ps;
struct regmap *regmap;
int period_ns;
struct clk *clk[FSL_PWM_CLK_MAX];
};
static inline struct fsl_pwm_chip *to_fsl_chip(struct pwm_chip *chip)
{
return container_of(chip, struct fsl_pwm_chip, chip);
}
static int fsl_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct fsl_pwm_chip *fpc = to_fsl_chip(chip);
return clk_prepare_enable(fpc->clk[FSL_PWM_CLK_SYS]);
}
static void fsl_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct fsl_pwm_chip *fpc = to_fsl_chip(chip);
clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_SYS]);
}
static int fsl_pwm_calculate_default_ps(struct fsl_pwm_chip *fpc,
enum fsl_pwm_clk index)
{
unsigned long sys_rate, cnt_rate;
unsigned long long ratio;
sys_rate = clk_get_rate(fpc->clk[FSL_PWM_CLK_SYS]);
if (!sys_rate)
return -EINVAL;
cnt_rate = clk_get_rate(fpc->clk[fpc->cnt_select]);
if (!cnt_rate)
return -EINVAL;
switch (index) {
case FSL_PWM_CLK_SYS:
fpc->clk_ps = 1;
break;
case FSL_PWM_CLK_FIX:
ratio = 2 * cnt_rate - 1;
do_div(ratio, sys_rate);
fpc->clk_ps = ratio;
break;
case FSL_PWM_CLK_EXT:
ratio = 4 * cnt_rate - 1;
do_div(ratio, sys_rate);
fpc->clk_ps = ratio;
break;
default:
return -EINVAL;
}
return 0;
}
static unsigned long fsl_pwm_calculate_cycles(struct fsl_pwm_chip *fpc,
unsigned long period_ns)
{
unsigned long long c, c0;
c = clk_get_rate(fpc->clk[fpc->cnt_select]);
c = c * period_ns;
do_div(c, 1000000000UL);
do {
c0 = c;
do_div(c0, (1 << fpc->clk_ps));
if (c0 <= 0xFFFF)
return (unsigned long)c0;
} while (++fpc->clk_ps < 8);
return 0;
}
static unsigned long fsl_pwm_calculate_period_cycles(struct fsl_pwm_chip *fpc,
unsigned long period_ns,
enum fsl_pwm_clk index)
{
int ret;
ret = fsl_pwm_calculate_default_ps(fpc, index);
if (ret) {
dev_err(fpc->chip.dev,
"failed to calculate default prescaler: %d\n",
ret);
return 0;
}
return fsl_pwm_calculate_cycles(fpc, period_ns);
}
static unsigned long fsl_pwm_calculate_period(struct fsl_pwm_chip *fpc,
unsigned long period_ns)
{
enum fsl_pwm_clk m0, m1;
unsigned long fix_rate, ext_rate, cycles;
cycles = fsl_pwm_calculate_period_cycles(fpc, period_ns,
FSL_PWM_CLK_SYS);
if (cycles) {
fpc->cnt_select = FSL_PWM_CLK_SYS;
return cycles;
}
fix_rate = clk_get_rate(fpc->clk[FSL_PWM_CLK_FIX]);
ext_rate = clk_get_rate(fpc->clk[FSL_PWM_CLK_EXT]);
if (fix_rate > ext_rate) {
m0 = FSL_PWM_CLK_FIX;
m1 = FSL_PWM_CLK_EXT;
} else {
m0 = FSL_PWM_CLK_EXT;
m1 = FSL_PWM_CLK_FIX;
}
cycles = fsl_pwm_calculate_period_cycles(fpc, period_ns, m0);
if (cycles) {
fpc->cnt_select = m0;
return cycles;
}
fpc->cnt_select = m1;
return fsl_pwm_calculate_period_cycles(fpc, period_ns, m1);
}
static unsigned long fsl_pwm_calculate_duty(struct fsl_pwm_chip *fpc,
unsigned long period_ns,
unsigned long duty_ns)
{
unsigned long long duty;
u32 val;
regmap_read(fpc->regmap, FTM_MOD, &val);
duty = (unsigned long long)duty_ns * (val + 1);
do_div(duty, period_ns);
return (unsigned long)duty;
}
static int fsl_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct fsl_pwm_chip *fpc = to_fsl_chip(chip);
u32 period, duty;
mutex_lock(&fpc->lock);
/*
* The Freescale FTM controller supports only a single period for
* all PWM channels, therefore incompatible changes need to be
* refused.
*/
if (fpc->period_ns && fpc->period_ns != period_ns) {
dev_err(fpc->chip.dev,
"conflicting period requested for PWM %u\n",
pwm->hwpwm);
mutex_unlock(&fpc->lock);
return -EBUSY;
}
if (!fpc->period_ns && duty_ns) {
period = fsl_pwm_calculate_period(fpc, period_ns);
if (!period) {
dev_err(fpc->chip.dev, "failed to calculate period\n");
mutex_unlock(&fpc->lock);
return -EINVAL;
}
regmap_update_bits(fpc->regmap, FTM_SC, FTM_SC_PS_MASK,
fpc->clk_ps);
regmap_write(fpc->regmap, FTM_MOD, period - 1);
fpc->period_ns = period_ns;
}
mutex_unlock(&fpc->lock);
duty = fsl_pwm_calculate_duty(fpc, period_ns, duty_ns);
regmap_write(fpc->regmap, FTM_CSC(pwm->hwpwm),
FTM_CSC_MSB | FTM_CSC_ELSB);
regmap_write(fpc->regmap, FTM_CV(pwm->hwpwm), duty);
return 0;
}
static int fsl_pwm_set_polarity(struct pwm_chip *chip,
struct pwm_device *pwm,
enum pwm_polarity polarity)
{
struct fsl_pwm_chip *fpc = to_fsl_chip(chip);
u32 val;
regmap_read(fpc->regmap, FTM_POL, &val);
if (polarity == PWM_POLARITY_INVERSED)
val |= BIT(pwm->hwpwm);
else
val &= ~BIT(pwm->hwpwm);
regmap_write(fpc->regmap, FTM_POL, val);
return 0;
}
static int fsl_counter_clock_enable(struct fsl_pwm_chip *fpc)
{
int ret;
if (fpc->use_count != 0)
return 0;
/* select counter clock source */
regmap_update_bits(fpc->regmap, FTM_SC, FTM_SC_CLK_MASK,
FTM_SC_CLK(fpc->cnt_select));
ret = clk_prepare_enable(fpc->clk[fpc->cnt_select]);
if (ret)
return ret;
ret = clk_prepare_enable(fpc->clk[FSL_PWM_CLK_CNTEN]);
if (ret) {
clk_disable_unprepare(fpc->clk[fpc->cnt_select]);
return ret;
}
fpc->use_count++;
return 0;
}
static int fsl_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct fsl_pwm_chip *fpc = to_fsl_chip(chip);
int ret;
mutex_lock(&fpc->lock);
regmap_update_bits(fpc->regmap, FTM_OUTMASK, BIT(pwm->hwpwm), 0);
ret = fsl_counter_clock_enable(fpc);
mutex_unlock(&fpc->lock);
return ret;
}
static void fsl_counter_clock_disable(struct fsl_pwm_chip *fpc)
{
/*
* already disabled, do nothing
*/
if (fpc->use_count == 0)
return;
/* there are still users, so can't disable yet */
if (--fpc->use_count > 0)
return;
/* no users left, disable PWM counter clock */
regmap_update_bits(fpc->regmap, FTM_SC, FTM_SC_CLK_MASK, 0);
clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_CNTEN]);
clk_disable_unprepare(fpc->clk[fpc->cnt_select]);
}
static void fsl_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct fsl_pwm_chip *fpc = to_fsl_chip(chip);
u32 val;
mutex_lock(&fpc->lock);
regmap_update_bits(fpc->regmap, FTM_OUTMASK, BIT(pwm->hwpwm),
BIT(pwm->hwpwm));
fsl_counter_clock_disable(fpc);
regmap_read(fpc->regmap, FTM_OUTMASK, &val);
if ((val & 0xFF) == 0xFF)
fpc->period_ns = 0;
mutex_unlock(&fpc->lock);
}
static const struct pwm_ops fsl_pwm_ops = {
.request = fsl_pwm_request,
.free = fsl_pwm_free,
.config = fsl_pwm_config,
.set_polarity = fsl_pwm_set_polarity,
.enable = fsl_pwm_enable,
.disable = fsl_pwm_disable,
.owner = THIS_MODULE,
};
static int fsl_pwm_init(struct fsl_pwm_chip *fpc)
{
int ret;
ret = clk_prepare_enable(fpc->clk[FSL_PWM_CLK_SYS]);
if (ret)
return ret;
regmap_write(fpc->regmap, FTM_CNTIN, 0x00);
regmap_write(fpc->regmap, FTM_OUTINIT, 0x00);
regmap_write(fpc->regmap, FTM_OUTMASK, 0xFF);
clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_SYS]);
return 0;
}
static const struct regmap_config fsl_pwm_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = FTM_PWMLOAD,
};
static int fsl_pwm_probe(struct platform_device *pdev)
{
struct fsl_pwm_chip *fpc;
struct resource *res;
void __iomem *base;
int ret;
fpc = devm_kzalloc(&pdev->dev, sizeof(*fpc), GFP_KERNEL);
if (!fpc)
return -ENOMEM;
mutex_init(&fpc->lock);
fpc->chip.dev = &pdev->dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
fpc->regmap = devm_regmap_init_mmio_clk(&pdev->dev, NULL, base,
&fsl_pwm_regmap_config);
if (IS_ERR(fpc->regmap)) {
dev_err(&pdev->dev, "regmap init failed\n");
return PTR_ERR(fpc->regmap);
}
fpc->clk[FSL_PWM_CLK_SYS] = devm_clk_get(&pdev->dev, "ftm_sys");
if (IS_ERR(fpc->clk[FSL_PWM_CLK_SYS])) {
dev_err(&pdev->dev, "failed to get \"ftm_sys\" clock\n");
return PTR_ERR(fpc->clk[FSL_PWM_CLK_SYS]);
}
fpc->clk[FSL_PWM_CLK_FIX] = devm_clk_get(fpc->chip.dev, "ftm_fix");
if (IS_ERR(fpc->clk[FSL_PWM_CLK_FIX]))
return PTR_ERR(fpc->clk[FSL_PWM_CLK_FIX]);
fpc->clk[FSL_PWM_CLK_EXT] = devm_clk_get(fpc->chip.dev, "ftm_ext");
if (IS_ERR(fpc->clk[FSL_PWM_CLK_EXT]))
return PTR_ERR(fpc->clk[FSL_PWM_CLK_EXT]);
fpc->clk[FSL_PWM_CLK_CNTEN] =
devm_clk_get(fpc->chip.dev, "ftm_cnt_clk_en");
if (IS_ERR(fpc->clk[FSL_PWM_CLK_CNTEN]))
return PTR_ERR(fpc->clk[FSL_PWM_CLK_CNTEN]);
fpc->chip.ops = &fsl_pwm_ops;
fpc->chip.of_xlate = of_pwm_xlate_with_flags;
fpc->chip.of_pwm_n_cells = 3;
fpc->chip.base = -1;
fpc->chip.npwm = 8;
fpc->chip.can_sleep = true;
ret = pwmchip_add(&fpc->chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, fpc);
return fsl_pwm_init(fpc);
}
static int fsl_pwm_remove(struct platform_device *pdev)
{
struct fsl_pwm_chip *fpc = platform_get_drvdata(pdev);
return pwmchip_remove(&fpc->chip);
}
static const struct of_device_id fsl_pwm_dt_ids[] = {
{ .compatible = "fsl,vf610-ftm-pwm", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fsl_pwm_dt_ids);
static struct platform_driver fsl_pwm_driver = {
.driver = {
.name = "fsl-ftm-pwm",
.of_match_table = fsl_pwm_dt_ids,
},
.probe = fsl_pwm_probe,
.remove = fsl_pwm_remove,
};
module_platform_driver(fsl_pwm_driver);
MODULE_DESCRIPTION("Freescale FlexTimer Module PWM Driver");
MODULE_AUTHOR("Xiubo Li <Li.Xiubo@freescale.com>");
MODULE_ALIAS("platform:fsl-ftm-pwm");
MODULE_LICENSE("GPL");

349
drivers/pwm/pwm-imx.c Normal file
View file

@ -0,0 +1,349 @@
/*
* 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/delay.h>
#include <linux/io.h>
#include <linux/pwm.h>
#include <linux/of.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_PWMSR 0x04 /* PWM Status 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_SWR (1 << 3)
#define MX3_PWMCR_EN (1 << 0)
#define MX3_PWMSR_FIFOAV_4WORDS 0x4
#define MX3_PWMSR_FIFOAV_MASK 0x7
#define MX3_PWM_SWR_LOOP 5
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);
struct device *dev = chip->dev;
unsigned long long c;
unsigned long period_cycles, duty_cycles, prescale;
unsigned int period_ms;
bool enable = test_bit(PWMF_ENABLED, &pwm->flags);
int wait_count = 0, fifoav;
u32 cr, sr;
/*
* i.MX PWMv2 has a 4-word sample FIFO.
* In order to avoid FIFO overflow issue, we do software reset
* to clear all sample FIFO if the controller is disabled or
* wait for a full PWM cycle to get a relinquished FIFO slot
* when the controller is enabled and the FIFO is fully loaded.
*/
if (enable) {
sr = readl(imx->mmio_base + MX3_PWMSR);
fifoav = sr & MX3_PWMSR_FIFOAV_MASK;
if (fifoav == MX3_PWMSR_FIFOAV_4WORDS) {
period_ms = DIV_ROUND_UP(pwm->period, NSEC_PER_MSEC);
msleep(period_ms);
sr = readl(imx->mmio_base + MX3_PWMSR);
if (fifoav == (sr & MX3_PWMSR_FIFOAV_MASK))
dev_warn(dev, "there is no free FIFO slot\n");
}
} else {
writel(MX3_PWMCR_SWR, imx->mmio_base + MX3_PWMCR);
do {
usleep_range(200, 1000);
cr = readl(imx->mmio_base + MX3_PWMCR);
} while ((cr & MX3_PWMCR_SWR) &&
(wait_count++ < MX3_PWM_SWR_LOOP));
if (cr & MX3_PWMCR_SWR)
dev_warn(dev, "software reset timeout\n");
}
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 (enable)
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)
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;
imx->chip.can_sleep = true;
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",
.owner = THIS_MODULE,
.of_match_table = 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>");

207
drivers/pwm/pwm-jz4740.c Normal file
View file

@ -0,0 +1,207 @@
/*
* 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;
jz4740 = devm_kzalloc(&pdev->dev, sizeof(*jz4740), GFP_KERNEL);
if (!jz4740)
return -ENOMEM;
jz4740->clk = devm_clk_get(&pdev->dev, "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;
platform_set_drvdata(pdev, jz4740);
return pwmchip_add(&jz4740->chip);
}
static int jz4740_pwm_remove(struct platform_device *pdev)
{
struct jz4740_pwm_chip *jz4740 = platform_get_drvdata(pdev);
return pwmchip_remove(&jz4740->chip);
}
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");

317
drivers/pwm/pwm-lp3943.c Normal file
View file

@ -0,0 +1,317 @@
/*
* TI/National Semiconductor LP3943 PWM driver
*
* Copyright 2013 Texas Instruments
*
* Author: Milo Kim <milo.kim@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; version 2.
*/
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/mfd/lp3943.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#define LP3943_MAX_DUTY 255
#define LP3943_MIN_PERIOD 6250
#define LP3943_MAX_PERIOD 1600000
struct lp3943_pwm {
struct pwm_chip chip;
struct lp3943 *lp3943;
struct lp3943_platform_data *pdata;
};
static inline struct lp3943_pwm *to_lp3943_pwm(struct pwm_chip *_chip)
{
return container_of(_chip, struct lp3943_pwm, chip);
}
static struct lp3943_pwm_map *
lp3943_pwm_request_map(struct lp3943_pwm *lp3943_pwm, int hwpwm)
{
struct lp3943_platform_data *pdata = lp3943_pwm->pdata;
struct lp3943 *lp3943 = lp3943_pwm->lp3943;
struct lp3943_pwm_map *pwm_map;
int i, offset;
pwm_map = kzalloc(sizeof(*pwm_map), GFP_KERNEL);
if (!pwm_map)
return ERR_PTR(-ENOMEM);
pwm_map->output = pdata->pwms[hwpwm]->output;
pwm_map->num_outputs = pdata->pwms[hwpwm]->num_outputs;
for (i = 0; i < pwm_map->num_outputs; i++) {
offset = pwm_map->output[i];
/* Return an error if the pin is already assigned */
if (test_and_set_bit(offset, &lp3943->pin_used)) {
kfree(pwm_map);
return ERR_PTR(-EBUSY);
}
}
return pwm_map;
}
static int lp3943_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct lp3943_pwm *lp3943_pwm = to_lp3943_pwm(chip);
struct lp3943_pwm_map *pwm_map;
pwm_map = lp3943_pwm_request_map(lp3943_pwm, pwm->hwpwm);
if (IS_ERR(pwm_map))
return PTR_ERR(pwm_map);
return pwm_set_chip_data(pwm, pwm_map);
}
static void lp3943_pwm_free_map(struct lp3943_pwm *lp3943_pwm,
struct lp3943_pwm_map *pwm_map)
{
struct lp3943 *lp3943 = lp3943_pwm->lp3943;
int i, offset;
for (i = 0; i < pwm_map->num_outputs; i++) {
offset = pwm_map->output[i];
clear_bit(offset, &lp3943->pin_used);
}
kfree(pwm_map);
}
static void lp3943_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct lp3943_pwm *lp3943_pwm = to_lp3943_pwm(chip);
struct lp3943_pwm_map *pwm_map = pwm_get_chip_data(pwm);
lp3943_pwm_free_map(lp3943_pwm, pwm_map);
}
static int lp3943_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct lp3943_pwm *lp3943_pwm = to_lp3943_pwm(chip);
struct lp3943 *lp3943 = lp3943_pwm->lp3943;
u8 val, reg_duty, reg_prescale;
int err;
/*
* How to configure the LP3943 PWMs
*
* 1) Period = 6250 ~ 1600000
* 2) Prescale = period / 6250 -1
* 3) Duty = input duty
*
* Prescale and duty are register values
*/
if (pwm->hwpwm == 0) {
reg_prescale = LP3943_REG_PRESCALE0;
reg_duty = LP3943_REG_PWM0;
} else {
reg_prescale = LP3943_REG_PRESCALE1;
reg_duty = LP3943_REG_PWM1;
}
period_ns = clamp(period_ns, LP3943_MIN_PERIOD, LP3943_MAX_PERIOD);
val = (u8)(period_ns / LP3943_MIN_PERIOD - 1);
err = lp3943_write_byte(lp3943, reg_prescale, val);
if (err)
return err;
val = (u8)(duty_ns * LP3943_MAX_DUTY / period_ns);
return lp3943_write_byte(lp3943, reg_duty, val);
}
static int lp3943_pwm_set_mode(struct lp3943_pwm *lp3943_pwm,
struct lp3943_pwm_map *pwm_map,
u8 val)
{
struct lp3943 *lp3943 = lp3943_pwm->lp3943;
const struct lp3943_reg_cfg *mux = lp3943->mux_cfg;
int i, index, err;
for (i = 0; i < pwm_map->num_outputs; i++) {
index = pwm_map->output[i];
err = lp3943_update_bits(lp3943, mux[index].reg,
mux[index].mask,
val << mux[index].shift);
if (err)
return err;
}
return 0;
}
static int lp3943_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct lp3943_pwm *lp3943_pwm = to_lp3943_pwm(chip);
struct lp3943_pwm_map *pwm_map = pwm_get_chip_data(pwm);
u8 val;
if (pwm->hwpwm == 0)
val = LP3943_DIM_PWM0;
else
val = LP3943_DIM_PWM1;
/*
* Each PWM generator is set to control any of outputs of LP3943.
* To enable/disable the PWM, these output pins should be configured.
*/
return lp3943_pwm_set_mode(lp3943_pwm, pwm_map, val);
}
static void lp3943_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct lp3943_pwm *lp3943_pwm = to_lp3943_pwm(chip);
struct lp3943_pwm_map *pwm_map = pwm_get_chip_data(pwm);
/*
* LP3943 outputs are open-drain, so the pin should be configured
* when the PWM is disabled.
*/
lp3943_pwm_set_mode(lp3943_pwm, pwm_map, LP3943_GPIO_OUT_HIGH);
}
static const struct pwm_ops lp3943_pwm_ops = {
.request = lp3943_pwm_request,
.free = lp3943_pwm_free,
.config = lp3943_pwm_config,
.enable = lp3943_pwm_enable,
.disable = lp3943_pwm_disable,
.owner = THIS_MODULE,
};
static int lp3943_pwm_parse_dt(struct device *dev,
struct lp3943_pwm *lp3943_pwm)
{
static const char * const name[] = { "ti,pwm0", "ti,pwm1", };
struct device_node *node = dev->of_node;
struct lp3943_platform_data *pdata;
struct lp3943_pwm_map *pwm_map;
enum lp3943_pwm_output *output;
int i, err, proplen, count = 0;
u32 num_outputs;
if (!node)
return -EINVAL;
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
/*
* Read the output map configuration from the device tree.
* Each of the two PWM generators can drive zero or more outputs.
*/
for (i = 0; i < LP3943_NUM_PWMS; i++) {
if (!of_get_property(node, name[i], &proplen))
continue;
num_outputs = proplen / sizeof(u32);
if (num_outputs == 0)
continue;
output = devm_kzalloc(dev, sizeof(*output) * num_outputs,
GFP_KERNEL);
if (!output)
return -ENOMEM;
err = of_property_read_u32_array(node, name[i], output,
num_outputs);
if (err)
return err;
pwm_map = devm_kzalloc(dev, sizeof(*pwm_map), GFP_KERNEL);
if (!pwm_map)
return -ENOMEM;
pwm_map->output = output;
pwm_map->num_outputs = num_outputs;
pdata->pwms[i] = pwm_map;
count++;
}
if (count == 0)
return -ENODATA;
lp3943_pwm->pdata = pdata;
return 0;
}
static int lp3943_pwm_probe(struct platform_device *pdev)
{
struct lp3943 *lp3943 = dev_get_drvdata(pdev->dev.parent);
struct lp3943_pwm *lp3943_pwm;
int ret;
lp3943_pwm = devm_kzalloc(&pdev->dev, sizeof(*lp3943_pwm), GFP_KERNEL);
if (!lp3943_pwm)
return -ENOMEM;
lp3943_pwm->pdata = lp3943->pdata;
if (!lp3943_pwm->pdata) {
if (IS_ENABLED(CONFIG_OF))
ret = lp3943_pwm_parse_dt(&pdev->dev, lp3943_pwm);
else
ret = -ENODEV;
if (ret)
return ret;
}
lp3943_pwm->lp3943 = lp3943;
lp3943_pwm->chip.dev = &pdev->dev;
lp3943_pwm->chip.ops = &lp3943_pwm_ops;
lp3943_pwm->chip.npwm = LP3943_NUM_PWMS;
lp3943_pwm->chip.can_sleep = true;
platform_set_drvdata(pdev, lp3943_pwm);
return pwmchip_add(&lp3943_pwm->chip);
}
static int lp3943_pwm_remove(struct platform_device *pdev)
{
struct lp3943_pwm *lp3943_pwm = platform_get_drvdata(pdev);
return pwmchip_remove(&lp3943_pwm->chip);
}
#ifdef CONFIG_OF
static const struct of_device_id lp3943_pwm_of_match[] = {
{ .compatible = "ti,lp3943-pwm", },
{ }
};
MODULE_DEVICE_TABLE(of, lp3943_pwm_of_match);
#endif
static struct platform_driver lp3943_pwm_driver = {
.probe = lp3943_pwm_probe,
.remove = lp3943_pwm_remove,
.driver = {
.name = "lp3943-pwm",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(lp3943_pwm_of_match),
},
};
module_platform_driver(lp3943_pwm_driver);
MODULE_DESCRIPTION("LP3943 PWM driver");
MODULE_ALIAS("platform:lp3943-pwm");
MODULE_AUTHOR("Milo Kim");
MODULE_LICENSE("GPL");

182
drivers/pwm/pwm-lpc32xx.c Normal file
View file

@ -0,0 +1,182 @@
/*
* 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);
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",
.owner = THIS_MODULE,
.of_match_table = 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");

View file

@ -0,0 +1,64 @@
/*
* Intel Low Power Subsystem PWM controller PCI driver
*
* Copyright (C) 2014, Intel Corporation
*
* Derived from the original pwm-lpss.c
*
* 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/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include "pwm-lpss.h"
static int pwm_lpss_probe_pci(struct pci_dev *pdev,
const struct pci_device_id *id)
{
const struct pwm_lpss_boardinfo *info;
struct pwm_lpss_chip *lpwm;
int err;
err = pcim_enable_device(pdev);
if (err < 0)
return err;
info = (struct pwm_lpss_boardinfo *)id->driver_data;
lpwm = pwm_lpss_probe(&pdev->dev, &pdev->resource[0], info);
if (IS_ERR(lpwm))
return PTR_ERR(lpwm);
pci_set_drvdata(pdev, lpwm);
return 0;
}
static void pwm_lpss_remove_pci(struct pci_dev *pdev)
{
struct pwm_lpss_chip *lpwm = pci_get_drvdata(pdev);
pwm_lpss_remove(lpwm);
}
static const struct pci_device_id pwm_lpss_pci_ids[] = {
{ PCI_VDEVICE(INTEL, 0x0f08), (unsigned long)&pwm_lpss_byt_info},
{ PCI_VDEVICE(INTEL, 0x0f09), (unsigned long)&pwm_lpss_byt_info},
{ PCI_VDEVICE(INTEL, 0x2288), (unsigned long)&pwm_lpss_bsw_info},
{ PCI_VDEVICE(INTEL, 0x2289), (unsigned long)&pwm_lpss_bsw_info},
{ },
};
MODULE_DEVICE_TABLE(pci, pwm_lpss_pci_ids);
static struct pci_driver pwm_lpss_driver_pci = {
.name = "pwm-lpss",
.id_table = pwm_lpss_pci_ids,
.probe = pwm_lpss_probe_pci,
.remove = pwm_lpss_remove_pci,
};
module_pci_driver(pwm_lpss_driver_pci);
MODULE_DESCRIPTION("PWM PCI driver for Intel LPSS");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,68 @@
/*
* Intel Low Power Subsystem PWM controller driver
*
* Copyright (C) 2014, Intel Corporation
*
* Derived from the original pwm-lpss.c
*
* 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/acpi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include "pwm-lpss.h"
static int pwm_lpss_probe_platform(struct platform_device *pdev)
{
const struct pwm_lpss_boardinfo *info;
const struct acpi_device_id *id;
struct pwm_lpss_chip *lpwm;
struct resource *r;
id = acpi_match_device(pdev->dev.driver->acpi_match_table, &pdev->dev);
if (!id)
return -ENODEV;
info = (const struct pwm_lpss_boardinfo *)id->driver_data;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
lpwm = pwm_lpss_probe(&pdev->dev, r, info);
if (IS_ERR(lpwm))
return PTR_ERR(lpwm);
platform_set_drvdata(pdev, lpwm);
return 0;
}
static int pwm_lpss_remove_platform(struct platform_device *pdev)
{
struct pwm_lpss_chip *lpwm = platform_get_drvdata(pdev);
return pwm_lpss_remove(lpwm);
}
static const struct acpi_device_id pwm_lpss_acpi_match[] = {
{ "80860F09", (unsigned long)&pwm_lpss_byt_info },
{ "80862288", (unsigned long)&pwm_lpss_bsw_info },
{ },
};
MODULE_DEVICE_TABLE(acpi, pwm_lpss_acpi_match);
static struct platform_driver pwm_lpss_driver_platform = {
.driver = {
.name = "pwm-lpss",
.acpi_match_table = pwm_lpss_acpi_match,
},
.probe = pwm_lpss_probe_platform,
.remove = pwm_lpss_remove_platform,
};
module_platform_driver(pwm_lpss_driver_platform);
MODULE_DESCRIPTION("PWM platform driver for Intel LPSS");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:pwm-lpss");

163
drivers/pwm/pwm-lpss.c Normal file
View file

@ -0,0 +1,163 @@
/*
* Intel Low Power Subsystem PWM controller driver
*
* Copyright (C) 2014, Intel Corporation
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
* Author: Chew Kean Ho <kean.ho.chew@intel.com>
* Author: Chang Rebecca Swee Fun <rebecca.swee.fun.chang@intel.com>
* Author: Chew Chiau Ee <chiau.ee.chew@intel.com>
* Author: Alan Cox <alan@linux.intel.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.
*/
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include "pwm-lpss.h"
#define PWM 0x00000000
#define PWM_ENABLE BIT(31)
#define PWM_SW_UPDATE BIT(30)
#define PWM_BASE_UNIT_SHIFT 8
#define PWM_BASE_UNIT_MASK 0x00ffff00
#define PWM_ON_TIME_DIV_MASK 0x000000ff
#define PWM_DIVISION_CORRECTION 0x2
#define PWM_LIMIT (0x8000 + PWM_DIVISION_CORRECTION)
#define NSECS_PER_SEC 1000000000UL
struct pwm_lpss_chip {
struct pwm_chip chip;
void __iomem *regs;
unsigned long clk_rate;
};
/* BayTrail */
const struct pwm_lpss_boardinfo pwm_lpss_byt_info = {
.clk_rate = 25000000
};
EXPORT_SYMBOL_GPL(pwm_lpss_byt_info);
/* Braswell */
const struct pwm_lpss_boardinfo pwm_lpss_bsw_info = {
.clk_rate = 19200000
};
EXPORT_SYMBOL_GPL(pwm_lpss_bsw_info);
static inline struct pwm_lpss_chip *to_lpwm(struct pwm_chip *chip)
{
return container_of(chip, struct pwm_lpss_chip, chip);
}
static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct pwm_lpss_chip *lpwm = to_lpwm(chip);
u8 on_time_div;
unsigned long c;
unsigned long long base_unit, freq = NSECS_PER_SEC;
u32 ctrl;
do_div(freq, period_ns);
/* The equation is: base_unit = ((freq / c) * 65536) + correction */
base_unit = freq * 65536;
c = lpwm->clk_rate;
if (!c)
return -EINVAL;
do_div(base_unit, c);
base_unit += PWM_DIVISION_CORRECTION;
if (base_unit > PWM_LIMIT)
return -EINVAL;
if (duty_ns <= 0)
duty_ns = 1;
on_time_div = 255 - (255 * duty_ns / period_ns);
ctrl = readl(lpwm->regs + PWM);
ctrl &= ~(PWM_BASE_UNIT_MASK | PWM_ON_TIME_DIV_MASK);
ctrl |= (u16) base_unit << PWM_BASE_UNIT_SHIFT;
ctrl |= on_time_div;
/* request PWM to update on next cycle */
ctrl |= PWM_SW_UPDATE;
writel(ctrl, lpwm->regs + PWM);
return 0;
}
static int pwm_lpss_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct pwm_lpss_chip *lpwm = to_lpwm(chip);
u32 ctrl;
ctrl = readl(lpwm->regs + PWM);
writel(ctrl | PWM_ENABLE, lpwm->regs + PWM);
return 0;
}
static void pwm_lpss_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct pwm_lpss_chip *lpwm = to_lpwm(chip);
u32 ctrl;
ctrl = readl(lpwm->regs + PWM);
writel(ctrl & ~PWM_ENABLE, lpwm->regs + PWM);
}
static const struct pwm_ops pwm_lpss_ops = {
.config = pwm_lpss_config,
.enable = pwm_lpss_enable,
.disable = pwm_lpss_disable,
.owner = THIS_MODULE,
};
struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r,
const struct pwm_lpss_boardinfo *info)
{
struct pwm_lpss_chip *lpwm;
int ret;
lpwm = devm_kzalloc(dev, sizeof(*lpwm), GFP_KERNEL);
if (!lpwm)
return ERR_PTR(-ENOMEM);
lpwm->regs = devm_ioremap_resource(dev, r);
if (IS_ERR(lpwm->regs))
return ERR_CAST(lpwm->regs);
lpwm->clk_rate = info->clk_rate;
lpwm->chip.dev = dev;
lpwm->chip.ops = &pwm_lpss_ops;
lpwm->chip.base = -1;
lpwm->chip.npwm = 1;
ret = pwmchip_add(&lpwm->chip);
if (ret) {
dev_err(dev, "failed to add PWM chip: %d\n", ret);
return ERR_PTR(ret);
}
return lpwm;
}
EXPORT_SYMBOL_GPL(pwm_lpss_probe);
int pwm_lpss_remove(struct pwm_lpss_chip *lpwm)
{
u32 ctrl;
ctrl = readl(lpwm->regs + PWM);
writel(ctrl & ~PWM_ENABLE, lpwm->regs + PWM);
return pwmchip_remove(&lpwm->chip);
}
EXPORT_SYMBOL_GPL(pwm_lpss_remove);
MODULE_DESCRIPTION("PWM driver for Intel LPSS");
MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
MODULE_LICENSE("GPL v2");

32
drivers/pwm/pwm-lpss.h Normal file
View file

@ -0,0 +1,32 @@
/*
* Intel Low Power Subsystem PWM controller driver
*
* Copyright (C) 2014, Intel Corporation
*
* Derived from the original pwm-lpss.c
*
* 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.
*/
#ifndef __PWM_LPSS_H
#define __PWM_LPSS_H
#include <linux/device.h>
#include <linux/pwm.h>
struct pwm_lpss_chip;
struct pwm_lpss_boardinfo {
unsigned long clk_rate;
};
extern const struct pwm_lpss_boardinfo pwm_lpss_byt_info;
extern const struct pwm_lpss_boardinfo pwm_lpss_bsw_info;
struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r,
const struct pwm_lpss_boardinfo *info);
int pwm_lpss_remove(struct pwm_lpss_chip *lpwm);
#endif /* __PWM_LPSS_H */

203
drivers/pwm/pwm-mxs.c Normal file
View file

@ -0,0 +1,203 @@
/*
* 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/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;
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);
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;
mxs->chip.can_sleep = true;
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);
ret = stmp_reset_block(mxs->base);
if (ret)
goto pwm_remove;
return 0;
pwm_remove:
pwmchip_remove(&mxs->chip);
return ret;
}
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",
.owner = THIS_MODULE,
.of_match_table = 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");

300
drivers/pwm/pwm-pca9685.c Normal file
View file

@ -0,0 +1,300 @@
/*
* Driver for PCA9685 16-channel 12-bit PWM LED controller
*
* Copyright (C) 2013 Steffen Trumtrar <s.trumtrar@pengutronix.de>
*
* based on the pwm-twl-led.c driver
*
* 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/i2c.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#define PCA9685_MODE1 0x00
#define PCA9685_MODE2 0x01
#define PCA9685_SUBADDR1 0x02
#define PCA9685_SUBADDR2 0x03
#define PCA9685_SUBADDR3 0x04
#define PCA9685_ALLCALLADDR 0x05
#define PCA9685_LEDX_ON_L 0x06
#define PCA9685_LEDX_ON_H 0x07
#define PCA9685_LEDX_OFF_L 0x08
#define PCA9685_LEDX_OFF_H 0x09
#define PCA9685_ALL_LED_ON_L 0xFA
#define PCA9685_ALL_LED_ON_H 0xFB
#define PCA9685_ALL_LED_OFF_L 0xFC
#define PCA9685_ALL_LED_OFF_H 0xFD
#define PCA9685_PRESCALE 0xFE
#define PCA9685_NUMREGS 0xFF
#define PCA9685_MAXCHAN 0x10
#define LED_FULL (1 << 4)
#define MODE1_SLEEP (1 << 4)
#define MODE2_INVRT (1 << 4)
#define MODE2_OUTDRV (1 << 2)
#define LED_N_ON_H(N) (PCA9685_LEDX_ON_H + (4 * (N)))
#define LED_N_ON_L(N) (PCA9685_LEDX_ON_L + (4 * (N)))
#define LED_N_OFF_H(N) (PCA9685_LEDX_OFF_H + (4 * (N)))
#define LED_N_OFF_L(N) (PCA9685_LEDX_OFF_L + (4 * (N)))
struct pca9685 {
struct pwm_chip chip;
struct regmap *regmap;
int active_cnt;
};
static inline struct pca9685 *to_pca(struct pwm_chip *chip)
{
return container_of(chip, struct pca9685, chip);
}
static int pca9685_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct pca9685 *pca = to_pca(chip);
unsigned long long duty;
unsigned int reg;
if (duty_ns < 1) {
if (pwm->hwpwm >= PCA9685_MAXCHAN)
reg = PCA9685_ALL_LED_OFF_H;
else
reg = LED_N_OFF_H(pwm->hwpwm);
regmap_write(pca->regmap, reg, LED_FULL);
return 0;
}
if (duty_ns == period_ns) {
if (pwm->hwpwm >= PCA9685_MAXCHAN)
reg = PCA9685_ALL_LED_ON_H;
else
reg = LED_N_ON_H(pwm->hwpwm);
regmap_write(pca->regmap, reg, LED_FULL);
return 0;
}
duty = 4096 * (unsigned long long)duty_ns;
duty = DIV_ROUND_UP_ULL(duty, period_ns);
if (pwm->hwpwm >= PCA9685_MAXCHAN)
reg = PCA9685_ALL_LED_OFF_L;
else
reg = LED_N_OFF_L(pwm->hwpwm);
regmap_write(pca->regmap, reg, (int)duty & 0xff);
if (pwm->hwpwm >= PCA9685_MAXCHAN)
reg = PCA9685_ALL_LED_OFF_H;
else
reg = LED_N_OFF_H(pwm->hwpwm);
regmap_write(pca->regmap, reg, ((int)duty >> 8) & 0xf);
return 0;
}
static int pca9685_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct pca9685 *pca = to_pca(chip);
unsigned int reg;
/*
* The PWM subsystem does not support a pre-delay.
* So, set the ON-timeout to 0
*/
if (pwm->hwpwm >= PCA9685_MAXCHAN)
reg = PCA9685_ALL_LED_ON_L;
else
reg = LED_N_ON_L(pwm->hwpwm);
regmap_write(pca->regmap, reg, 0);
if (pwm->hwpwm >= PCA9685_MAXCHAN)
reg = PCA9685_ALL_LED_ON_H;
else
reg = LED_N_ON_H(pwm->hwpwm);
regmap_write(pca->regmap, reg, 0);
/*
* Clear the full-off bit.
* It has precedence over the others and must be off.
*/
if (pwm->hwpwm >= PCA9685_MAXCHAN)
reg = PCA9685_ALL_LED_OFF_H;
else
reg = LED_N_OFF_H(pwm->hwpwm);
regmap_update_bits(pca->regmap, reg, LED_FULL, 0x0);
return 0;
}
static void pca9685_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct pca9685 *pca = to_pca(chip);
unsigned int reg;
if (pwm->hwpwm >= PCA9685_MAXCHAN)
reg = PCA9685_ALL_LED_OFF_H;
else
reg = LED_N_OFF_H(pwm->hwpwm);
regmap_write(pca->regmap, reg, LED_FULL);
/* Clear the LED_OFF counter. */
if (pwm->hwpwm >= PCA9685_MAXCHAN)
reg = PCA9685_ALL_LED_OFF_L;
else
reg = LED_N_OFF_L(pwm->hwpwm);
regmap_write(pca->regmap, reg, 0x0);
}
static int pca9685_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct pca9685 *pca = to_pca(chip);
if (pca->active_cnt++ == 0)
return regmap_update_bits(pca->regmap, PCA9685_MODE1,
MODE1_SLEEP, 0x0);
return 0;
}
static void pca9685_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct pca9685 *pca = to_pca(chip);
if (--pca->active_cnt == 0)
regmap_update_bits(pca->regmap, PCA9685_MODE1, MODE1_SLEEP,
MODE1_SLEEP);
}
static const struct pwm_ops pca9685_pwm_ops = {
.enable = pca9685_pwm_enable,
.disable = pca9685_pwm_disable,
.config = pca9685_pwm_config,
.request = pca9685_pwm_request,
.free = pca9685_pwm_free,
.owner = THIS_MODULE,
};
static struct regmap_config pca9685_regmap_i2c_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = PCA9685_NUMREGS,
.cache_type = REGCACHE_NONE,
};
static int pca9685_pwm_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device_node *np = client->dev.of_node;
struct pca9685 *pca;
int ret;
int mode2;
pca = devm_kzalloc(&client->dev, sizeof(*pca), GFP_KERNEL);
if (!pca)
return -ENOMEM;
pca->regmap = devm_regmap_init_i2c(client, &pca9685_regmap_i2c_config);
if (IS_ERR(pca->regmap)) {
ret = PTR_ERR(pca->regmap);
dev_err(&client->dev, "Failed to initialize register map: %d\n",
ret);
return ret;
}
i2c_set_clientdata(client, pca);
regmap_read(pca->regmap, PCA9685_MODE2, &mode2);
if (of_property_read_bool(np, "invert"))
mode2 |= MODE2_INVRT;
else
mode2 &= ~MODE2_INVRT;
if (of_property_read_bool(np, "open-drain"))
mode2 &= ~MODE2_OUTDRV;
else
mode2 |= MODE2_OUTDRV;
regmap_write(pca->regmap, PCA9685_MODE2, mode2);
/* clear all "full off" bits */
regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_L, 0);
regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_H, 0);
pca->chip.ops = &pca9685_pwm_ops;
/* add an extra channel for ALL_LED */
pca->chip.npwm = PCA9685_MAXCHAN + 1;
pca->chip.dev = &client->dev;
pca->chip.base = -1;
pca->chip.can_sleep = true;
return pwmchip_add(&pca->chip);
}
static int pca9685_pwm_remove(struct i2c_client *client)
{
struct pca9685 *pca = i2c_get_clientdata(client);
regmap_update_bits(pca->regmap, PCA9685_MODE1, MODE1_SLEEP,
MODE1_SLEEP);
return pwmchip_remove(&pca->chip);
}
static const struct i2c_device_id pca9685_id[] = {
{ "pca9685", 0 },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(i2c, pca9685_id);
static const struct of_device_id pca9685_dt_ids[] = {
{ .compatible = "nxp,pca9685-pwm", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, pca9685_dt_ids);
static struct i2c_driver pca9685_i2c_driver = {
.driver = {
.name = "pca9685-pwm",
.owner = THIS_MODULE,
.of_match_table = pca9685_dt_ids,
},
.probe = pca9685_pwm_probe,
.remove = pca9685_pwm_remove,
.id_table = pca9685_id,
};
module_i2c_driver(pca9685_i2c_driver);
MODULE_AUTHOR("Steffen Trumtrar <s.trumtrar@pengutronix.de>");
MODULE_DESCRIPTION("PWM driver for PCA9685");
MODULE_LICENSE("GPL");

156
drivers/pwm/pwm-puv3.c Normal file
View file

@ -0,0 +1,156 @@
/*
* 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",
.owner = THIS_MODULE,
},
.probe = pwm_probe,
.remove = pwm_remove,
};
module_platform_driver(puv3_pwm_driver);
MODULE_LICENSE("GPL v2");

238
drivers/pwm/pwm-pxa.c Normal file
View file

@ -0,0 +1,238 @@
/*
* 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 <linux/of_device.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,
};
#ifdef CONFIG_OF
/*
* Device tree users must create one device instance for each PWM channel.
* Hence we dispense with the HAS_SECONDARY_PWM and "tell" the original driver
* code that this is a single channel pxa25x-pwm. Currently all devices are
* supported identically.
*/
static const struct of_device_id pwm_of_match[] = {
{ .compatible = "marvell,pxa250-pwm", .data = &pwm_id_table[0]},
{ .compatible = "marvell,pxa270-pwm", .data = &pwm_id_table[0]},
{ .compatible = "marvell,pxa168-pwm", .data = &pwm_id_table[0]},
{ .compatible = "marvell,pxa910-pwm", .data = &pwm_id_table[0]},
{ }
};
MODULE_DEVICE_TABLE(of, pwm_of_match);
#else
#define pwm_of_match NULL
#endif
static const struct platform_device_id *pxa_pwm_get_id_dt(struct device *dev)
{
const struct of_device_id *id = of_match_device(pwm_of_match, dev);
return id ? id->data : NULL;
}
static struct pwm_device *
pxa_pwm_of_xlate(struct pwm_chip *pc, const struct of_phandle_args *args)
{
struct pwm_device *pwm;
pwm = pwm_request_from_chip(pc, 0, NULL);
if (IS_ERR(pwm))
return pwm;
pwm_set_period(pwm, args->args[0]);
return pwm;
}
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;
if (IS_ENABLED(CONFIG_OF) && id == NULL)
id = pxa_pwm_get_id_dt(&pdev->dev);
if (id == NULL)
return -EINVAL;
pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
if (pwm == NULL)
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;
if (IS_ENABLED(CONFIG_OF)) {
pwm->chip.of_xlate = pxa_pwm_of_xlate;
pwm->chip.of_pwm_n_cells = 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,
.of_match_table = pwm_of_match,
},
.probe = pwm_probe,
.remove = pwm_remove,
.id_table = pwm_id_table,
};
module_platform_driver(pwm_driver);
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,481 @@
/*
* R-Mobile TPU PWM driver
*
* Copyright (C) 2012 Renesas Solutions Corp.
*
* 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
*
* 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/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#define TPU_CHANNEL_MAX 4
#define TPU_TSTR 0x00 /* Timer start register (shared) */
#define TPU_TCRn 0x00 /* Timer control register */
#define TPU_TCR_CCLR_NONE (0 << 5)
#define TPU_TCR_CCLR_TGRA (1 << 5)
#define TPU_TCR_CCLR_TGRB (2 << 5)
#define TPU_TCR_CCLR_TGRC (5 << 5)
#define TPU_TCR_CCLR_TGRD (6 << 5)
#define TPU_TCR_CKEG_RISING (0 << 3)
#define TPU_TCR_CKEG_FALLING (1 << 3)
#define TPU_TCR_CKEG_BOTH (2 << 3)
#define TPU_TMDRn 0x04 /* Timer mode register */
#define TPU_TMDR_BFWT (1 << 6)
#define TPU_TMDR_BFB (1 << 5)
#define TPU_TMDR_BFA (1 << 4)
#define TPU_TMDR_MD_NORMAL (0 << 0)
#define TPU_TMDR_MD_PWM (2 << 0)
#define TPU_TIORn 0x08 /* Timer I/O control register */
#define TPU_TIOR_IOA_0 (0 << 0)
#define TPU_TIOR_IOA_0_CLR (1 << 0)
#define TPU_TIOR_IOA_0_SET (2 << 0)
#define TPU_TIOR_IOA_0_TOGGLE (3 << 0)
#define TPU_TIOR_IOA_1 (4 << 0)
#define TPU_TIOR_IOA_1_CLR (5 << 0)
#define TPU_TIOR_IOA_1_SET (6 << 0)
#define TPU_TIOR_IOA_1_TOGGLE (7 << 0)
#define TPU_TIERn 0x0c /* Timer interrupt enable register */
#define TPU_TSRn 0x10 /* Timer status register */
#define TPU_TCNTn 0x14 /* Timer counter */
#define TPU_TGRAn 0x18 /* Timer general register A */
#define TPU_TGRBn 0x1c /* Timer general register B */
#define TPU_TGRCn 0x20 /* Timer general register C */
#define TPU_TGRDn 0x24 /* Timer general register D */
#define TPU_CHANNEL_OFFSET 0x10
#define TPU_CHANNEL_SIZE 0x40
enum tpu_pin_state {
TPU_PIN_INACTIVE, /* Pin is driven inactive */
TPU_PIN_PWM, /* Pin is driven by PWM */
TPU_PIN_ACTIVE, /* Pin is driven active */
};
struct tpu_device;
struct tpu_pwm_device {
bool timer_on; /* Whether the timer is running */
struct tpu_device *tpu;
unsigned int channel; /* Channel number in the TPU */
enum pwm_polarity polarity;
unsigned int prescaler;
u16 period;
u16 duty;
};
struct tpu_device {
struct platform_device *pdev;
struct pwm_chip chip;
spinlock_t lock;
void __iomem *base;
struct clk *clk;
};
#define to_tpu_device(c) container_of(c, struct tpu_device, chip)
static void tpu_pwm_write(struct tpu_pwm_device *pwm, int reg_nr, u16 value)
{
void __iomem *base = pwm->tpu->base + TPU_CHANNEL_OFFSET
+ pwm->channel * TPU_CHANNEL_SIZE;
iowrite16(value, base + reg_nr);
}
static void tpu_pwm_set_pin(struct tpu_pwm_device *pwm,
enum tpu_pin_state state)
{
static const char * const states[] = { "inactive", "PWM", "active" };
dev_dbg(&pwm->tpu->pdev->dev, "%u: configuring pin as %s\n",
pwm->channel, states[state]);
switch (state) {
case TPU_PIN_INACTIVE:
tpu_pwm_write(pwm, TPU_TIORn,
pwm->polarity == PWM_POLARITY_INVERSED ?
TPU_TIOR_IOA_1 : TPU_TIOR_IOA_0);
break;
case TPU_PIN_PWM:
tpu_pwm_write(pwm, TPU_TIORn,
pwm->polarity == PWM_POLARITY_INVERSED ?
TPU_TIOR_IOA_0_SET : TPU_TIOR_IOA_1_CLR);
break;
case TPU_PIN_ACTIVE:
tpu_pwm_write(pwm, TPU_TIORn,
pwm->polarity == PWM_POLARITY_INVERSED ?
TPU_TIOR_IOA_0 : TPU_TIOR_IOA_1);
break;
}
}
static void tpu_pwm_start_stop(struct tpu_pwm_device *pwm, int start)
{
unsigned long flags;
u16 value;
spin_lock_irqsave(&pwm->tpu->lock, flags);
value = ioread16(pwm->tpu->base + TPU_TSTR);
if (start)
value |= 1 << pwm->channel;
else
value &= ~(1 << pwm->channel);
iowrite16(value, pwm->tpu->base + TPU_TSTR);
spin_unlock_irqrestore(&pwm->tpu->lock, flags);
}
static int tpu_pwm_timer_start(struct tpu_pwm_device *pwm)
{
int ret;
if (!pwm->timer_on) {
/* Wake up device and enable clock. */
pm_runtime_get_sync(&pwm->tpu->pdev->dev);
ret = clk_prepare_enable(pwm->tpu->clk);
if (ret) {
dev_err(&pwm->tpu->pdev->dev, "cannot enable clock\n");
return ret;
}
pwm->timer_on = true;
}
/*
* Make sure the channel is stopped, as we need to reconfigure it
* completely. First drive the pin to the inactive state to avoid
* glitches.
*/
tpu_pwm_set_pin(pwm, TPU_PIN_INACTIVE);
tpu_pwm_start_stop(pwm, false);
/*
* - Clear TCNT on TGRB match
* - Count on rising edge
* - Set prescaler
* - Output 0 until TGRA, output 1 until TGRB (active low polarity)
* - Output 1 until TGRA, output 0 until TGRB (active high polarity
* - PWM mode
*/
tpu_pwm_write(pwm, TPU_TCRn, TPU_TCR_CCLR_TGRB | TPU_TCR_CKEG_RISING |
pwm->prescaler);
tpu_pwm_write(pwm, TPU_TMDRn, TPU_TMDR_MD_PWM);
tpu_pwm_set_pin(pwm, TPU_PIN_PWM);
tpu_pwm_write(pwm, TPU_TGRAn, pwm->duty);
tpu_pwm_write(pwm, TPU_TGRBn, pwm->period);
dev_dbg(&pwm->tpu->pdev->dev, "%u: TGRA 0x%04x TGRB 0x%04x\n",
pwm->channel, pwm->duty, pwm->period);
/* Start the channel. */
tpu_pwm_start_stop(pwm, true);
return 0;
}
static void tpu_pwm_timer_stop(struct tpu_pwm_device *pwm)
{
if (!pwm->timer_on)
return;
/* Disable channel. */
tpu_pwm_start_stop(pwm, false);
/* Stop clock and mark device as idle. */
clk_disable_unprepare(pwm->tpu->clk);
pm_runtime_put(&pwm->tpu->pdev->dev);
pwm->timer_on = false;
}
/* -----------------------------------------------------------------------------
* PWM API
*/
static int tpu_pwm_request(struct pwm_chip *chip, struct pwm_device *_pwm)
{
struct tpu_device *tpu = to_tpu_device(chip);
struct tpu_pwm_device *pwm;
if (_pwm->hwpwm >= TPU_CHANNEL_MAX)
return -EINVAL;
pwm = kzalloc(sizeof(*pwm), GFP_KERNEL);
if (pwm == NULL)
return -ENOMEM;
pwm->tpu = tpu;
pwm->channel = _pwm->hwpwm;
pwm->polarity = PWM_POLARITY_NORMAL;
pwm->prescaler = 0;
pwm->period = 0;
pwm->duty = 0;
pwm->timer_on = false;
pwm_set_chip_data(_pwm, pwm);
return 0;
}
static void tpu_pwm_free(struct pwm_chip *chip, struct pwm_device *_pwm)
{
struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
tpu_pwm_timer_stop(pwm);
kfree(pwm);
}
static int tpu_pwm_config(struct pwm_chip *chip, struct pwm_device *_pwm,
int duty_ns, int period_ns)
{
static const unsigned int prescalers[] = { 1, 4, 16, 64 };
struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
struct tpu_device *tpu = to_tpu_device(chip);
unsigned int prescaler;
bool duty_only = false;
u32 clk_rate;
u32 period;
u32 duty;
int ret;
/*
* Pick a prescaler to avoid overflowing the counter.
* TODO: Pick the highest acceptable prescaler.
*/
clk_rate = clk_get_rate(tpu->clk);
for (prescaler = 0; prescaler < ARRAY_SIZE(prescalers); ++prescaler) {
period = clk_rate / prescalers[prescaler]
/ (NSEC_PER_SEC / period_ns);
if (period <= 0xffff)
break;
}
if (prescaler == ARRAY_SIZE(prescalers) || period == 0) {
dev_err(&tpu->pdev->dev, "clock rate mismatch\n");
return -ENOTSUPP;
}
if (duty_ns) {
duty = clk_rate / prescalers[prescaler]
/ (NSEC_PER_SEC / duty_ns);
if (duty > period)
return -EINVAL;
} else {
duty = 0;
}
dev_dbg(&tpu->pdev->dev,
"rate %u, prescaler %u, period %u, duty %u\n",
clk_rate, prescalers[prescaler], period, duty);
if (pwm->prescaler == prescaler && pwm->period == period)
duty_only = true;
pwm->prescaler = prescaler;
pwm->period = period;
pwm->duty = duty;
/* If the channel is disabled we're done. */
if (!test_bit(PWMF_ENABLED, &_pwm->flags))
return 0;
if (duty_only && pwm->timer_on) {
/*
* If only the duty cycle changed and the timer is already
* running, there's no need to reconfigure it completely, Just
* modify the duty cycle.
*/
tpu_pwm_write(pwm, TPU_TGRAn, pwm->duty);
dev_dbg(&tpu->pdev->dev, "%u: TGRA 0x%04x\n", pwm->channel,
pwm->duty);
} else {
/* Otherwise perform a full reconfiguration. */
ret = tpu_pwm_timer_start(pwm);
if (ret < 0)
return ret;
}
if (duty == 0 || duty == period) {
/*
* To avoid running the timer when not strictly required, handle
* 0% and 100% duty cycles as fixed levels and stop the timer.
*/
tpu_pwm_set_pin(pwm, duty ? TPU_PIN_ACTIVE : TPU_PIN_INACTIVE);
tpu_pwm_timer_stop(pwm);
}
return 0;
}
static int tpu_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *_pwm,
enum pwm_polarity polarity)
{
struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
pwm->polarity = polarity;
return 0;
}
static int tpu_pwm_enable(struct pwm_chip *chip, struct pwm_device *_pwm)
{
struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
int ret;
ret = tpu_pwm_timer_start(pwm);
if (ret < 0)
return ret;
/*
* To avoid running the timer when not strictly required, handle 0% and
* 100% duty cycles as fixed levels and stop the timer.
*/
if (pwm->duty == 0 || pwm->duty == pwm->period) {
tpu_pwm_set_pin(pwm, pwm->duty ?
TPU_PIN_ACTIVE : TPU_PIN_INACTIVE);
tpu_pwm_timer_stop(pwm);
}
return 0;
}
static void tpu_pwm_disable(struct pwm_chip *chip, struct pwm_device *_pwm)
{
struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
/* The timer must be running to modify the pin output configuration. */
tpu_pwm_timer_start(pwm);
tpu_pwm_set_pin(pwm, TPU_PIN_INACTIVE);
tpu_pwm_timer_stop(pwm);
}
static const struct pwm_ops tpu_pwm_ops = {
.request = tpu_pwm_request,
.free = tpu_pwm_free,
.config = tpu_pwm_config,
.set_polarity = tpu_pwm_set_polarity,
.enable = tpu_pwm_enable,
.disable = tpu_pwm_disable,
.owner = THIS_MODULE,
};
/* -----------------------------------------------------------------------------
* Probe and remove
*/
static int tpu_probe(struct platform_device *pdev)
{
struct tpu_device *tpu;
struct resource *res;
int ret;
tpu = devm_kzalloc(&pdev->dev, sizeof(*tpu), GFP_KERNEL);
if (tpu == NULL)
return -ENOMEM;
spin_lock_init(&tpu->lock);
tpu->pdev = pdev;
/* Map memory, get clock and pin control. */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
tpu->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(tpu->base))
return PTR_ERR(tpu->base);
tpu->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(tpu->clk)) {
dev_err(&pdev->dev, "cannot get clock\n");
return PTR_ERR(tpu->clk);
}
/* Initialize and register the device. */
platform_set_drvdata(pdev, tpu);
tpu->chip.dev = &pdev->dev;
tpu->chip.ops = &tpu_pwm_ops;
tpu->chip.of_xlate = of_pwm_xlate_with_flags;
tpu->chip.of_pwm_n_cells = 3;
tpu->chip.base = -1;
tpu->chip.npwm = TPU_CHANNEL_MAX;
ret = pwmchip_add(&tpu->chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register PWM chip\n");
return ret;
}
dev_info(&pdev->dev, "TPU PWM %d registered\n", tpu->pdev->id);
pm_runtime_enable(&pdev->dev);
return 0;
}
static int tpu_remove(struct platform_device *pdev)
{
struct tpu_device *tpu = platform_get_drvdata(pdev);
int ret;
ret = pwmchip_remove(&tpu->chip);
if (ret)
return ret;
pm_runtime_disable(&pdev->dev);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id tpu_of_table[] = {
{ .compatible = "renesas,tpu-r8a73a4", },
{ .compatible = "renesas,tpu-r8a7740", },
{ .compatible = "renesas,tpu-r8a7790", },
{ .compatible = "renesas,tpu-sh7372", },
{ .compatible = "renesas,tpu", },
{ },
};
MODULE_DEVICE_TABLE(of, tpu_of_table);
#endif
static struct platform_driver tpu_driver = {
.probe = tpu_probe,
.remove = tpu_remove,
.driver = {
.name = "renesas-tpu-pwm",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(tpu_of_table),
}
};
module_platform_driver(tpu_driver);
MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
MODULE_DESCRIPTION("Renesas TPU PWM Driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:renesas-tpu-pwm");

303
drivers/pwm/pwm-rockchip.c Normal file
View file

@ -0,0 +1,303 @@
/*
* PWM driver for Rockchip SoCs
*
* Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.com>
* Copyright (C) 2014 ROCKCHIP, Inc.
*
* 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/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/time.h>
#define PWM_CTRL_TIMER_EN (1 << 0)
#define PWM_CTRL_OUTPUT_EN (1 << 3)
#define PWM_ENABLE (1 << 0)
#define PWM_CONTINUOUS (1 << 1)
#define PWM_DUTY_POSITIVE (1 << 3)
#define PWM_DUTY_NEGATIVE (0 << 3)
#define PWM_INACTIVE_NEGATIVE (0 << 4)
#define PWM_INACTIVE_POSITIVE (1 << 4)
#define PWM_OUTPUT_LEFT (0 << 5)
#define PWM_LP_DISABLE (0 << 8)
struct rockchip_pwm_chip {
struct pwm_chip chip;
struct clk *clk;
const struct rockchip_pwm_data *data;
void __iomem *base;
};
struct rockchip_pwm_regs {
unsigned long duty;
unsigned long period;
unsigned long cntr;
unsigned long ctrl;
};
struct rockchip_pwm_data {
struct rockchip_pwm_regs regs;
unsigned int prescaler;
const struct pwm_ops *ops;
void (*set_enable)(struct pwm_chip *chip,
struct pwm_device *pwm, bool enable);
};
static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c)
{
return container_of(c, struct rockchip_pwm_chip, chip);
}
static void rockchip_pwm_set_enable_v1(struct pwm_chip *chip,
struct pwm_device *pwm, bool enable)
{
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
u32 enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN;
u32 val;
val = readl_relaxed(pc->base + pc->data->regs.ctrl);
if (enable)
val |= enable_conf;
else
val &= ~enable_conf;
writel_relaxed(val, pc->base + pc->data->regs.ctrl);
}
static void rockchip_pwm_set_enable_v2(struct pwm_chip *chip,
struct pwm_device *pwm, bool enable)
{
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
u32 enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
PWM_CONTINUOUS;
u32 val;
if (pwm->polarity == PWM_POLARITY_INVERSED)
enable_conf |= PWM_DUTY_NEGATIVE | PWM_INACTIVE_POSITIVE;
else
enable_conf |= PWM_DUTY_POSITIVE | PWM_INACTIVE_NEGATIVE;
val = readl_relaxed(pc->base + pc->data->regs.ctrl);
if (enable)
val |= enable_conf;
else
val &= ~enable_conf;
writel_relaxed(val, pc->base + pc->data->regs.ctrl);
}
static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
unsigned long period, duty;
u64 clk_rate, div;
int ret;
clk_rate = clk_get_rate(pc->clk);
/*
* Since period and duty cycle registers have a width of 32
* bits, every possible input period can be obtained using the
* default prescaler value for all practical clock rate values.
*/
div = clk_rate * period_ns;
do_div(div, pc->data->prescaler * NSEC_PER_SEC);
period = div;
div = clk_rate * duty_ns;
do_div(div, pc->data->prescaler * NSEC_PER_SEC);
duty = div;
ret = clk_enable(pc->clk);
if (ret)
return ret;
writel(period, pc->base + pc->data->regs.period);
writel(duty, pc->base + pc->data->regs.duty);
writel(0, pc->base + pc->data->regs.cntr);
clk_disable(pc->clk);
return 0;
}
static int rockchip_pwm_set_polarity(struct pwm_chip *chip,
struct pwm_device *pwm,
enum pwm_polarity polarity)
{
/*
* No action needed here because pwm->polarity will be set by the core
* and the core will only change polarity when the PWM is not enabled.
* We'll handle things in set_enable().
*/
return 0;
}
static int rockchip_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
int ret;
ret = clk_enable(pc->clk);
if (ret)
return ret;
pc->data->set_enable(chip, pwm, true);
return 0;
}
static void rockchip_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
pc->data->set_enable(chip, pwm, false);
clk_disable(pc->clk);
}
static const struct pwm_ops rockchip_pwm_ops_v1 = {
.config = rockchip_pwm_config,
.enable = rockchip_pwm_enable,
.disable = rockchip_pwm_disable,
.owner = THIS_MODULE,
};
static const struct pwm_ops rockchip_pwm_ops_v2 = {
.config = rockchip_pwm_config,
.set_polarity = rockchip_pwm_set_polarity,
.enable = rockchip_pwm_enable,
.disable = rockchip_pwm_disable,
.owner = THIS_MODULE,
};
static const struct rockchip_pwm_data pwm_data_v1 = {
.regs = {
.duty = 0x04,
.period = 0x08,
.cntr = 0x00,
.ctrl = 0x0c,
},
.prescaler = 2,
.ops = &rockchip_pwm_ops_v1,
.set_enable = rockchip_pwm_set_enable_v1,
};
static const struct rockchip_pwm_data pwm_data_v2 = {
.regs = {
.duty = 0x08,
.period = 0x04,
.cntr = 0x00,
.ctrl = 0x0c,
},
.prescaler = 1,
.ops = &rockchip_pwm_ops_v2,
.set_enable = rockchip_pwm_set_enable_v2,
};
static const struct rockchip_pwm_data pwm_data_vop = {
.regs = {
.duty = 0x08,
.period = 0x04,
.cntr = 0x0c,
.ctrl = 0x00,
},
.prescaler = 1,
.ops = &rockchip_pwm_ops_v2,
.set_enable = rockchip_pwm_set_enable_v2,
};
static const struct of_device_id rockchip_pwm_dt_ids[] = {
{ .compatible = "rockchip,rk2928-pwm", .data = &pwm_data_v1},
{ .compatible = "rockchip,rk3288-pwm", .data = &pwm_data_v2},
{ .compatible = "rockchip,vop-pwm", .data = &pwm_data_vop},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_pwm_dt_ids);
static int rockchip_pwm_probe(struct platform_device *pdev)
{
const struct of_device_id *id;
struct rockchip_pwm_chip *pc;
struct resource *r;
int ret;
id = of_match_device(rockchip_pwm_dt_ids, &pdev->dev);
if (!id)
return -EINVAL;
pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
if (!pc)
return -ENOMEM;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
pc->base = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(pc->base))
return PTR_ERR(pc->base);
pc->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(pc->clk))
return PTR_ERR(pc->clk);
ret = clk_prepare(pc->clk);
if (ret)
return ret;
platform_set_drvdata(pdev, pc);
pc->data = id->data;
pc->chip.dev = &pdev->dev;
pc->chip.ops = pc->data->ops;
pc->chip.base = -1;
pc->chip.npwm = 1;
if (pc->data->ops->set_polarity) {
pc->chip.of_xlate = of_pwm_xlate_with_flags;
pc->chip.of_pwm_n_cells = 3;
}
ret = pwmchip_add(&pc->chip);
if (ret < 0) {
clk_unprepare(pc->clk);
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
}
return ret;
}
static int rockchip_pwm_remove(struct platform_device *pdev)
{
struct rockchip_pwm_chip *pc = platform_get_drvdata(pdev);
clk_unprepare(pc->clk);
return pwmchip_remove(&pc->chip);
}
static struct platform_driver rockchip_pwm_driver = {
.driver = {
.name = "rockchip-pwm",
.of_match_table = rockchip_pwm_dt_ids,
},
.probe = rockchip_pwm_probe,
.remove = rockchip_pwm_remove,
};
module_platform_driver(rockchip_pwm_driver);
MODULE_AUTHOR("Beniamino Galvani <b.galvani@gmail.com>");
MODULE_DESCRIPTION("Rockchip SoC PWM driver");
MODULE_LICENSE("GPL v2");

849
drivers/pwm/pwm-samsung.c Executable file
View file

@ -0,0 +1,849 @@
/*
* Copyright (c) 2007 Ben Dooks
* Copyright (c) 2008 Simtec Electronics
* Ben Dooks <ben@simtec.co.uk>, <ben-linux@fluff.org>
* Copyright (c) 2013 Tomasz Figa <tomasz.figa@gmail.com>
*
* PWM driver for Samsung SoCs
*
* 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.
*/
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/export.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/time.h>
/* For struct samsung_timer_variant and samsung_pwm_lock. */
#include <clocksource/samsung_pwm.h>
#include <soc/samsung/exynos-pm.h>
#include <soc/samsung/exynos-powermode.h>
#define REG_TCFG0 0x00
#define REG_TCFG1 0x04
#define REG_TCON 0x08
#define REG_TCNTB(chan) (0x0c + ((chan) * 0xc))
#define REG_TCMPB(chan) (0x10 + ((chan) * 0xc))
#define TCFG0_PRESCALER_MASK 0xff
#define TCFG0_PRESCALER1_SHIFT 8
#define TCFG1_MUX_MASK 0xf
#define TCFG1_SHIFT(chan) (4 * (chan))
/*
* Each channel occupies 4 bits in TCON register, but there is a gap of 4
* bits (one channel) after channel 0, so channels have different numbering
* when accessing TCON register. See to_tcon_channel() function.
*
* In addition, the location of autoreload bit for channel 4 (TCON channel 5)
* in its set of bits is 2 as opposed to 3 for other channels.
*/
#define TCON_START(chan) BIT(4 * (chan) + 0)
#define TCON_MANUALUPDATE(chan) BIT(4 * (chan) + 1)
#define TCON_INVERT(chan) BIT(4 * (chan) + 2)
#define _TCON_AUTORELOAD(chan) BIT(4 * (chan) + 3)
#define _TCON_AUTORELOAD4(chan) BIT(4 * (chan) + 2)
#define TCON_AUTORELOAD(chan) \
((chan < 5) ? _TCON_AUTORELOAD(chan) : _TCON_AUTORELOAD4(chan))
enum duty_cycle {
DUTY_CYCLE_ZERO,
DUTY_CYCLE_PULSE,
DUTY_CYCLE_FULL,
};
/**
* struct samsung_pwm_channel - private data of PWM channel
* @period_ns: current period in nanoseconds programmed to the hardware
* @duty_ns: current duty time in nanoseconds programmed to the hardware
* @tin_ns: time of one timer tick in nanoseconds with current timer rate
*/
struct samsung_pwm_channel {
struct clk *clk_div;
struct clk *clk_tin;
u32 period_ns;
u32 duty_ns;
u32 tin_ns;
unsigned char running;
enum duty_cycle duty_cycle;
};
/**
* struct samsung_pwm_chip - private data of PWM chip
* @chip: generic PWM chip
* @variant: local copy of hardware variant data
* @inverter_mask: inverter status for all channels - one bit per channel
* @base: base address of mapped PWM registers
* @base_clk: base clock used to drive the timers
*/
struct samsung_pwm_chip {
struct pwm_chip chip;
struct samsung_pwm_variant variant;
u8 inverter_mask;
void __iomem *base;
struct clk *base_clk;
struct clk *sclk;
unsigned int reg_tcfg0;
int enable_cnt;
unsigned int need_hw_init;
unsigned int idle_ip_index;
bool sclk_ctrl;
};
#ifndef CONFIG_CLKSRC_SAMSUNG_PWM
/*
* PWM block is shared between pwm-samsung and samsung_pwm_timer drivers
* and some registers need access synchronization. If both drivers are
* compiled in, the spinlock is defined in the clocksource driver,
* otherwise following definition is used.
*
* Currently we do not need any more complex synchronization method
* because all the supported SoCs contain only one instance of the PWM
* IP. Should this change, both drivers will need to be modified to
* properly synchronize accesses to particular instances.
*/
static DEFINE_SPINLOCK(samsung_pwm_lock);
#endif
struct samsung_pwm_chip *g_samsung_pwm;
static void pwm_samsung_save(struct samsung_pwm_chip *chip);
static void pwm_samsung_restore(struct samsung_pwm_chip *chip);
static int pwm_samsung_clk_enable(struct samsung_pwm_chip *chip)
{
int ret;
exynos_update_ip_idle_status(chip->idle_ip_index, 0);
ret = clk_enable(chip->base_clk);
if (ret)
goto base_clk_err;
if (chip->sclk_ctrl) {
ret = clk_enable(chip->sclk);
if (ret)
goto sclk_err;
}
return 0;
sclk_err:
clk_disable(chip->base_clk);
base_clk_err:
exynos_update_ip_idle_status(chip->idle_ip_index, 1);
return ret;
}
static void pwm_samsung_clk_disable(struct samsung_pwm_chip *chip)
{
if (chip->sclk_ctrl)
clk_disable(chip->sclk);
clk_disable(chip->base_clk);
exynos_update_ip_idle_status(chip->idle_ip_index, 1);
}
static inline
struct samsung_pwm_chip *to_samsung_pwm_chip(struct pwm_chip *chip)
{
return container_of(chip, struct samsung_pwm_chip, chip);
}
static inline unsigned int to_tcon_channel(unsigned int channel)
{
/* TCON register has a gap of 4 bits (1 channel) after channel 0 */
return (channel == 0) ? 0 : (channel + 1);
}
static void pwm_samsung_set_divisor(struct samsung_pwm_chip *chip,
unsigned int chan, unsigned long rate)
{
struct pwm_device *pwm = &chip->chip.pwms[chan];
struct samsung_pwm_channel *channel = pwm_get_chip_data(pwm);
clk_set_rate(channel->clk_div, rate);
}
static int pwm_samsung_is_tdiv(struct samsung_pwm_chip *chip, unsigned int chan)
{
struct pwm_device *pwm = &chip->chip.pwms[chan];
struct samsung_pwm_channel *channel = pwm_get_chip_data(pwm);
return clk_get_parent(channel->clk_tin) == channel->clk_div;
}
static unsigned long pwm_samsung_get_tin_rate(struct samsung_pwm_chip *chip,
unsigned int chan)
{
struct pwm_device *pwm = &chip->chip.pwms[chan];
struct samsung_pwm_channel *channel = pwm_get_chip_data(pwm);
unsigned long rate;
rate = clk_get_rate(clk_get_parent(channel->clk_div));
clk_set_rate(clk_get_parent(channel->clk_div), rate);
return rate;
}
static unsigned long pwm_samsung_calc_tin(struct samsung_pwm_chip *chip,
unsigned int chan, unsigned long freq)
{
struct pwm_device *pwm = &chip->chip.pwms[chan];
struct samsung_pwm_channel *channel = pwm_get_chip_data(pwm);
struct samsung_pwm_variant *variant = &chip->variant;
unsigned long rate;
u8 div;
if (!pwm_samsung_is_tdiv(chip, chan)) {
rate = clk_get_rate(channel->clk_tin);
if (rate)
return rate;
dev_warn(chip->chip.dev,
"tin of PWM %d is inoperational, using tdiv\n", chan);
}
rate = pwm_samsung_get_tin_rate(chip, chan);
dev_dbg(chip->chip.dev, "tin parent at %lu\n", rate);
/*
* Compare minimum PWM frequency that can be achieved with possible
* divider settings and choose the lowest divisor that can generate
* frequencies lower than requested.
*/
for (div = variant->div_base; div < 4; ++div)
if ((rate >> (variant->bits + div)) < freq)
break;
pwm_samsung_set_divisor(chip, chan, rate >> div);
dev_dbg(chip->chip.dev, "tdiv at %lu\n", clk_get_rate(channel->clk_div));
return rate >> div;
}
static void pwm_samsung_init(struct samsung_pwm_chip *chip,
struct pwm_device *pwm)
{
unsigned int tcon_chan = to_tcon_channel(pwm->hwpwm);
u32 tcon;
__raw_writel(0, chip->base + REG_TCMPB(pwm->hwpwm));
__raw_writel(0, chip->base + REG_TCNTB(pwm->hwpwm));
tcon = __raw_readl(chip->base + REG_TCON);
tcon |= TCON_INVERT(tcon_chan) | TCON_MANUALUPDATE(tcon_chan);
tcon &= ~(TCON_AUTORELOAD(tcon_chan) | TCON_START(tcon_chan));
__raw_writel(tcon, chip->base + REG_TCON);
tcon &= ~TCON_MANUALUPDATE(tcon_chan);
__raw_writel(tcon, chip->base + REG_TCON);
}
static int pwm_samsung_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip);
struct samsung_pwm_channel *our_chan;
unsigned char clk_tin_name[16];
unsigned char clk_tdiv_name[16];
unsigned long flags;
if (!(our_chip->variant.output_mask & BIT(pwm->hwpwm))) {
dev_warn(chip->dev,
"tried to request PWM channel %d without output\n",
pwm->hwpwm);
return -EINVAL;
}
our_chan = devm_kzalloc(chip->dev, sizeof(*our_chan), GFP_KERNEL);
if (!our_chan)
return -ENOMEM;
pwm_set_chip_data(pwm, our_chan);
snprintf(clk_tin_name, sizeof(clk_tin_name), "pwm-tin%d", pwm->hwpwm);
our_chan->clk_tin = devm_clk_get(chip->dev, clk_tin_name);
if (IS_ERR(our_chan->clk_tin)) {
dev_err(chip->dev, "failed to get pwm tin clk\n");
return PTR_ERR(our_chan->clk_tin);
}
snprintf(clk_tdiv_name, sizeof(clk_tdiv_name), "pwm-tdiv%d", pwm->hwpwm);
our_chan->clk_div = devm_clk_get(chip->dev, clk_tdiv_name);
if (IS_ERR(our_chan->clk_div)) {
dev_err(chip->dev, "failed to get pwm tdiv clk\n");
return PTR_ERR(our_chan->clk_div);
}
spin_lock_irqsave(&samsung_pwm_lock, flags);
pwm_samsung_clk_enable(our_chip);
pwm_samsung_init(our_chip, pwm);
pwm_samsung_clk_disable(our_chip);
spin_unlock_irqrestore(&samsung_pwm_lock, flags);
return 0;
}
static void pwm_samsung_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
devm_kfree(chip->dev, pwm_get_chip_data(pwm));
pwm_set_chip_data(pwm, NULL);
}
static int pwm_samsung_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip);
unsigned int tcon_chan = to_tcon_channel(pwm->hwpwm);
struct samsung_pwm_channel *channel = pwm_get_chip_data(pwm);
unsigned long flags;
u32 tcon;
spin_lock_irqsave(&samsung_pwm_lock, flags);
if (!our_chip->enable_cnt && !our_chip->need_hw_init)
pwm_samsung_clk_enable(our_chip);
if (our_chip->need_hw_init)
pwm_samsung_restore(our_chip);
tcon = __raw_readl(our_chip->base + REG_TCON);
if (!(tcon & TCON_START(tcon_chan))) {
tcon |= TCON_MANUALUPDATE(tcon_chan);
__raw_writel(tcon, our_chip->base + REG_TCON);
tcon &= ~TCON_MANUALUPDATE(tcon_chan);
if (channel->duty_cycle == DUTY_CYCLE_ZERO)
tcon &= ~TCON_AUTORELOAD(tcon_chan);
else
tcon |= TCON_AUTORELOAD(tcon_chan);
tcon |= TCON_START(tcon_chan);
__raw_writel(tcon, our_chip->base + REG_TCON);
} else if (!(tcon & TCON_AUTORELOAD(tcon_chan)) &&
channel->duty_cycle != DUTY_CYCLE_ZERO) {
tcon |= TCON_MANUALUPDATE(tcon_chan);
__raw_writel(tcon, our_chip->base + REG_TCON);
tcon &= ~TCON_MANUALUPDATE(tcon_chan);
tcon |= TCON_AUTORELOAD(tcon_chan);
__raw_writel(tcon, our_chip->base + REG_TCON);
}
channel->running = 1;
our_chip->enable_cnt++;
spin_unlock_irqrestore(&samsung_pwm_lock, flags);
return 0;
}
static void pwm_samsung_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip);
unsigned int tcon_chan = to_tcon_channel(pwm->hwpwm);
struct samsung_pwm_channel *channel = pwm_get_chip_data(pwm);
unsigned long flags;
u32 tcon;
spin_lock_irqsave(&samsung_pwm_lock, flags);
tcon = readl(our_chip->base + REG_TCON);
tcon &= ~TCON_AUTORELOAD(tcon_chan);
writel(tcon, our_chip->base + REG_TCON);
channel->running = 0;
our_chip->enable_cnt--;
if (!our_chip->enable_cnt && !our_chip->need_hw_init)
pwm_samsung_clk_disable(our_chip);
spin_unlock_irqrestore(&samsung_pwm_lock, flags);
}
static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip);
unsigned int tcon_chan = to_tcon_channel(pwm->hwpwm);
struct samsung_pwm_channel *chan = pwm_get_chip_data(pwm);
enum duty_cycle duty_cycle;
u32 tin_ns = chan->tin_ns, tcnt, tcmp, tcon;
unsigned long flags;
int ret = 0;
/*
* We currently avoid using 64bit arithmetic by using the
* fact that anything faster than 1Hz is easily representable
* by 32bits.
*/
if (period_ns > NSEC_PER_SEC)
return -ERANGE;
if (duty_ns > period_ns)
return -EINVAL;
if (period_ns == chan->period_ns && duty_ns == chan->duty_ns)
return 0;
pwm_samsung_clk_enable(our_chip);
dev_dbg(our_chip->chip.dev, "base_clk at %lu\n",
clk_get_rate(our_chip->base_clk));
if (our_chip->sclk_ctrl)
dev_dbg(our_chip->chip.dev, "sclk at %lu\n",
clk_get_rate(our_chip->sclk));
/* Check to see if we are changing the clock rate of the PWM. */
if (chan->period_ns != period_ns) {
unsigned long tin_rate;
u32 period;
period = NSEC_PER_SEC / period_ns;
dev_dbg(our_chip->chip.dev, "duty_ns=%d, period_ns=%d (%u)\n",
duty_ns, period_ns, period);
tin_rate = pwm_samsung_calc_tin(our_chip, pwm->hwpwm, period);
if(!tin_rate) {
ret = -EINVAL;
goto out;
}
tin_ns = NSEC_PER_SEC / tin_rate;
}
/* Note that counters count down. */
tcnt = DIV_ROUND_CLOSEST(period_ns, tin_ns);
tcmp = DIV_ROUND_CLOSEST(duty_ns, tin_ns);
/* Period is too short. */
if (tcnt <= 1) {
ret = -ERANGE;
goto out;
}
if (tcmp == 0)
duty_cycle = DUTY_CYCLE_ZERO;
else if (tcmp == tcnt)
duty_cycle = DUTY_CYCLE_FULL;
else
duty_cycle = DUTY_CYCLE_PULSE;
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 counts 1 hidden tick at the end of each period on S3C64XX and
* EXYNOS series, so tcmp and tcnt should be subtracted 1.
*/
/* Decrement to get tick numbers, instead of tick counts. */
--tcnt;
/* -1UL will give 100% duty. */
--tcmp;
dev_dbg(our_chip->chip.dev,
"tin_ns=%u, tcmp=%u/%u\n", tin_ns, tcmp, tcnt);
/* Update PWM registers. */
spin_lock_irqsave(&samsung_pwm_lock, flags);
if (our_chip->need_hw_init)
pwm_samsung_restore(our_chip);
writel(tcnt, our_chip->base + REG_TCNTB(pwm->hwpwm));
writel(tcmp, our_chip->base + REG_TCMPB(pwm->hwpwm));
tcon = __raw_readl(our_chip->base + REG_TCON);
if (chan->running == 1 && tcon & TCON_START(tcon_chan) &&
chan->duty_cycle != duty_cycle) {
if (duty_cycle == DUTY_CYCLE_ZERO) {
tcon |= TCON_MANUALUPDATE(tcon_chan);
__raw_writel(tcon, our_chip->base + REG_TCON);
tcon &= ~TCON_MANUALUPDATE(tcon_chan);
tcon &= ~TCON_AUTORELOAD(tcon_chan);
} else {
tcon |= TCON_AUTORELOAD(tcon_chan);
}
__raw_writel(tcon, our_chip->base + REG_TCON);
}
chan->period_ns = period_ns;
chan->tin_ns = tin_ns;
chan->duty_ns = duty_ns;
chan->duty_cycle = duty_cycle;
spin_unlock_irqrestore(&samsung_pwm_lock, flags);
out:
pwm_samsung_clk_disable(our_chip);
return ret;
}
static void pwm_samsung_set_invert(struct samsung_pwm_chip *chip,
unsigned int channel, bool invert)
{
unsigned int tcon_chan = to_tcon_channel(channel);
unsigned long flags;
u32 tcon;
spin_lock_irqsave(&samsung_pwm_lock, flags);
if (!chip->enable_cnt)
pwm_samsung_clk_enable(chip);
tcon = readl(chip->base + REG_TCON);
if (invert) {
chip->inverter_mask |= BIT(channel);
tcon |= TCON_INVERT(tcon_chan);
} else {
chip->inverter_mask &= ~BIT(channel);
tcon &= ~TCON_INVERT(tcon_chan);
}
writel(tcon, chip->base + REG_TCON);
if (!chip->enable_cnt)
pwm_samsung_clk_disable(chip);
spin_unlock_irqrestore(&samsung_pwm_lock, flags);
}
static int pwm_samsung_set_polarity(struct pwm_chip *chip,
struct pwm_device *pwm,
enum pwm_polarity polarity)
{
struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip);
bool invert = (polarity == PWM_POLARITY_NORMAL);
/* Inverted means normal in the hardware. */
pwm_samsung_set_invert(our_chip, pwm->hwpwm, invert);
return 0;
}
static const struct pwm_ops pwm_samsung_ops = {
.request = pwm_samsung_request,
.free = pwm_samsung_free,
.enable = pwm_samsung_enable,
.disable = pwm_samsung_disable,
.config = pwm_samsung_config,
.set_polarity = pwm_samsung_set_polarity,
.owner = THIS_MODULE,
};
#ifdef CONFIG_OF
static const struct samsung_pwm_variant s3c24xx_variant = {
.bits = 16,
.div_base = 1,
.has_tint_cstat = false,
.tclk_mask = BIT(4),
};
static const struct samsung_pwm_variant s3c64xx_variant = {
.bits = 16,
.div_base = 1,
.has_tint_cstat = true,
.tclk_mask = BIT(7) | BIT(6) | BIT(5),
};
static const struct samsung_pwm_variant s5p64x0_variant = {
.bits = 32,
.div_base = 0,
.has_tint_cstat = true,
.tclk_mask = 0,
};
static const struct samsung_pwm_variant s5pc100_variant = {
.bits = 32,
.div_base = 0,
.has_tint_cstat = true,
.tclk_mask = BIT(5),
};
static const struct of_device_id samsung_pwm_matches[] = {
{ .compatible = "samsung,s3c2410-pwm", .data = &s3c24xx_variant },
{ .compatible = "samsung,s3c6400-pwm", .data = &s3c64xx_variant },
{ .compatible = "samsung,s5p6440-pwm", .data = &s5p64x0_variant },
{ .compatible = "samsung,s5pc100-pwm", .data = &s5pc100_variant },
{ .compatible = "samsung,exynos4210-pwm", .data = &s5p64x0_variant },
{},
};
static int pwm_samsung_parse_dt(struct samsung_pwm_chip *chip)
{
struct device_node *np = chip->chip.dev->of_node;
const struct of_device_id *match;
struct property *prop;
const __be32 *cur;
u32 val;
match = of_match_node(samsung_pwm_matches, np);
if (!match)
return -ENODEV;
memcpy(&chip->variant, match->data, sizeof(chip->variant));
of_property_for_each_u32(np, "samsung,pwm-outputs", prop, cur, val) {
if (val >= SAMSUNG_PWM_NUM) {
dev_err(chip->chip.dev,
"%s: invalid channel index in samsung,pwm-outputs property\n",
__func__);
continue;
}
chip->variant.output_mask |= BIT(val);
}
if (of_find_property(np, "samsung,pwm-sclk-ctrl", NULL))
chip->sclk_ctrl = true;
else
chip->sclk_ctrl = false;
return 0;
}
#else
static int pwm_samsung_parse_dt(struct samsung_pwm_chip *chip)
{
return -ENODEV;
}
#endif
#ifdef CONFIG_CPU_IDLE
static int pwm_samsung_notifier(struct notifier_block *nb,
unsigned long cmd, void *v)
{
switch (cmd) {
case LPA_ENTER:
if (!g_samsung_pwm->need_hw_init && !g_samsung_pwm->enable_cnt)
pwm_samsung_save(g_samsung_pwm);
break;
case LPA_EXIT:
if (!g_samsung_pwm->need_hw_init && !g_samsung_pwm->enable_cnt)
g_samsung_pwm->need_hw_init = 1;
break;
}
return NOTIFY_OK;
}
static struct notifier_block pwm_samsung_notifier_block = {
.notifier_call = pwm_samsung_notifier,
};
#endif
static int pwm_samsung_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct samsung_pwm_chip *chip;
struct resource *res;
int ret;
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
if (chip == NULL)
return -ENOMEM;
chip->chip.dev = &pdev->dev;
chip->chip.ops = &pwm_samsung_ops;
chip->chip.base = -1;
chip->chip.npwm = SAMSUNG_PWM_NUM;
chip->inverter_mask = BIT(SAMSUNG_PWM_NUM) - 1;
chip->idle_ip_index = exynos_get_idle_ip_index(dev_name(&pdev->dev));
g_samsung_pwm = chip;
if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) {
ret = pwm_samsung_parse_dt(chip);
if (ret)
return ret;
chip->chip.of_xlate = of_pwm_xlate_with_flags;
chip->chip.of_pwm_n_cells = 3;
} else {
if (!pdev->dev.platform_data) {
dev_err(&pdev->dev, "no platform data specified\n");
return -EINVAL;
}
memcpy(&chip->variant, pdev->dev.platform_data,
sizeof(chip->variant));
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
chip->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(chip->base))
return PTR_ERR(chip->base);
chip->base_clk = devm_clk_get(&pdev->dev, "gate_timers");
if (IS_ERR(chip->base_clk)) {
dev_err(dev, "failed to get timer base clk\n");
return PTR_ERR(chip->base_clk);
}
if (chip->sclk_ctrl) {
chip->sclk = devm_clk_get(&pdev->dev, "sclk_pwm");
if (IS_ERR(chip->sclk)) {
dev_err(dev, "failed to get timer sclk-pwm\n");
return PTR_ERR(chip->sclk);
}
}
exynos_update_ip_idle_status(chip->idle_ip_index, 0);
ret = clk_prepare_enable(chip->base_clk);
if (ret)
goto base_clk_err;
if (chip->sclk_ctrl) {
ret = clk_prepare_enable(chip->sclk);
if (ret)
goto sclk_err;
}
dev_info(dev, "base_clk at %lu", clk_get_rate(chip->base_clk));
if (chip->sclk_ctrl)
dev_info(dev, "sclk at %lu", clk_get_rate(chip->sclk));
platform_set_drvdata(pdev, chip);
ret = pwmchip_add(&chip->chip);
if (ret < 0) {
dev_err(dev, "failed to register PWM chip\n");
goto chip_add_err;
}
pwm_samsung_clk_disable(chip);
#ifdef CONFIG_CPU_IDLE
exynos_pm_register_notifier(&pwm_samsung_notifier_block);
#endif
return 0;
chip_add_err:
if (chip->sclk_ctrl)
clk_disable_unprepare(chip->sclk);
sclk_err:
clk_disable_unprepare(chip->base_clk);
base_clk_err:
exynos_update_ip_idle_status(chip->idle_ip_index, 1);
return ret;
}
static int pwm_samsung_remove(struct platform_device *pdev)
{
struct samsung_pwm_chip *chip = platform_get_drvdata(pdev);
int ret;
ret = pwmchip_remove(&chip->chip);
if (ret < 0)
return ret;
if (chip->sclk_ctrl)
clk_disable_unprepare(chip->sclk);
clk_disable_unprepare(chip->base_clk);
return 0;
}
static void pwm_samsung_save(struct samsung_pwm_chip *chip)
{
u32 tcon;
unsigned int i;
if (!chip->enable_cnt && !chip->need_hw_init)
pwm_samsung_clk_enable(chip);
for (i = 0; i < SAMSUNG_PWM_NUM; ++i) {
struct pwm_device *pwm = &chip->chip.pwms[i];
struct samsung_pwm_channel *chan = pwm_get_chip_data(pwm);
unsigned int tcon_chan = to_tcon_channel(pwm->hwpwm);
if (!chan)
continue;
if (chan->running == 0) {
tcon = __raw_readl(chip->base + REG_TCON);
if (chan->duty_cycle == DUTY_CYCLE_ZERO) {
tcon |= TCON_MANUALUPDATE(tcon_chan);
} else if (chan->duty_cycle == DUTY_CYCLE_FULL) {
tcon &= TCON_INVERT(tcon_chan);
tcon |= TCON_MANUALUPDATE(tcon_chan);
}
tcon &= ~TCON_START(tcon_chan);
__raw_writel(tcon, chip->base + REG_TCON);
}
chan->period_ns = -1;
chan->duty_ns = -1;
}
/* Save pwm registers*/
if (chip->enable_cnt)
chip->reg_tcfg0 = __raw_readl(chip->base + REG_TCFG0);
pwm_samsung_clk_disable(chip);
}
static void pwm_samsung_restore(struct samsung_pwm_chip *chip)
{
unsigned int chan;
pwm_samsung_clk_enable(chip);
chip->need_hw_init = 0;
/* Restore pwm registers*/
if (chip->enable_cnt)
__raw_writel(chip->reg_tcfg0, chip->base + REG_TCFG0);
for (chan = 0; chan < SAMSUNG_PWM_NUM; ++chan) {
if (chip->variant.output_mask & BIT(chan)) {
struct pwm_device *pwm = &chip->chip.pwms[chan];
pwm_samsung_init(chip, pwm);
}
}
}
#ifdef CONFIG_PM_SLEEP
static int pwm_samsung_suspend(struct device *dev)
{
struct samsung_pwm_chip *chip = dev_get_drvdata(dev);
if(!chip->need_hw_init)
pwm_samsung_save(chip);
return 0;
}
static int pwm_samsung_resume(struct device *dev)
{
struct samsung_pwm_chip *chip = dev_get_drvdata(dev);
pwm_samsung_restore(chip);
if (!chip->enable_cnt)
pwm_samsung_clk_disable(chip);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(pwm_samsung_pm_ops, pwm_samsung_suspend,
pwm_samsung_resume);
static struct platform_driver pwm_samsung_driver = {
.driver = {
.name = "samsung-pwm",
.owner = THIS_MODULE,
.pm = &pwm_samsung_pm_ops,
.of_match_table = of_match_ptr(samsung_pwm_matches),
},
.probe = pwm_samsung_probe,
.remove = pwm_samsung_remove,
};
module_platform_driver(pwm_samsung_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tomasz Figa <tomasz.figa@gmail.com>");
MODULE_ALIAS("platform:samsung-pwm");

267
drivers/pwm/pwm-spear.c Normal file
View file

@ -0,0 +1,267 @@
/*
* ST Microelectronics SPEAr Pulse Width Modulator driver
*
* Copyright (C) 2012 ST Microelectronics
* Shiraz Hashim <shiraz.linux.kernel@gmail.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;
pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
if (!pc)
return -ENOMEM;
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);
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 < 0) {
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",
.owner = THIS_MODULE,
.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.linux.kernel@gmail.com>");
MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.com>");
MODULE_ALIAS("platform:spear-pwm");

418
drivers/pwm/pwm-sti.c Normal file
View file

@ -0,0 +1,418 @@
/*
* PWM device driver for ST SoCs.
* Author: Ajit Pal Singh <ajitpal.singh@st.com>
*
* Copyright (C) 2013-2014 STMicroelectronics (R&D) Limited
*
* 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.
*/
#include <linux/clk.h>
#include <linux/math64.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/time.h>
#define STI_DS_REG(ch) (4 * (ch)) /* Channel's Duty Cycle register */
#define STI_PWMCR 0x50 /* Control/Config register */
#define STI_INTEN 0x54 /* Interrupt Enable/Disable register */
#define PWM_PRESCALE_LOW_MASK 0x0f
#define PWM_PRESCALE_HIGH_MASK 0xf0
/* Regfield IDs */
enum {
PWMCLK_PRESCALE_LOW,
PWMCLK_PRESCALE_HIGH,
PWM_EN,
PWM_INT_EN,
/* Keep last */
MAX_REGFIELDS
};
struct sti_pwm_compat_data {
const struct reg_field *reg_fields;
unsigned int num_chan;
unsigned int max_pwm_cnt;
unsigned int max_prescale;
};
struct sti_pwm_chip {
struct device *dev;
struct clk *clk;
unsigned long clk_rate;
struct regmap *regmap;
struct sti_pwm_compat_data *cdata;
struct regmap_field *prescale_low;
struct regmap_field *prescale_high;
struct regmap_field *pwm_en;
struct regmap_field *pwm_int_en;
struct pwm_chip chip;
struct pwm_device *cur;
unsigned int en_count;
struct mutex sti_pwm_lock; /* To sync between enable/disable calls */
void __iomem *mmio;
};
static const struct reg_field sti_pwm_regfields[MAX_REGFIELDS] = {
[PWMCLK_PRESCALE_LOW] = REG_FIELD(STI_PWMCR, 0, 3),
[PWMCLK_PRESCALE_HIGH] = REG_FIELD(STI_PWMCR, 11, 14),
[PWM_EN] = REG_FIELD(STI_PWMCR, 9, 9),
[PWM_INT_EN] = REG_FIELD(STI_INTEN, 0, 0),
};
static inline struct sti_pwm_chip *to_sti_pwmchip(struct pwm_chip *chip)
{
return container_of(chip, struct sti_pwm_chip, chip);
}
/*
* Calculate the prescaler value corresponding to the period.
*/
static int sti_pwm_get_prescale(struct sti_pwm_chip *pc, unsigned long period,
unsigned int *prescale)
{
struct sti_pwm_compat_data *cdata = pc->cdata;
unsigned long val;
unsigned int ps;
/*
* prescale = ((period_ns * clk_rate) / (10^9 * (max_pwm_count + 1)) - 1
*/
val = NSEC_PER_SEC / pc->clk_rate;
val *= cdata->max_pwm_cnt + 1;
if (period % val) {
return -EINVAL;
} else {
ps = period / val - 1;
if (ps > cdata->max_prescale)
return -EINVAL;
}
*prescale = ps;
return 0;
}
/* Calculate the number of PWM devices configured with a period. */
static unsigned int sti_pwm_count_configured(struct pwm_chip *chip)
{
struct pwm_device *pwm;
unsigned int ncfg = 0;
unsigned int i;
for (i = 0; i < chip->npwm; i++) {
pwm = &chip->pwms[i];
if (test_bit(PWMF_REQUESTED, &pwm->flags)) {
if (pwm_get_period(pwm))
ncfg++;
}
}
return ncfg;
}
/*
* For STiH4xx PWM IP, the PWM period is fixed to 256 local clock cycles.
* The only way to change the period (apart from changing the PWM input clock)
* is to change the PWM clock prescaler.
* The prescaler is of 8 bits, so 256 prescaler values and hence
* 256 possible period values are supported (for a particular clock rate).
* The requested period will be applied only if it matches one of these
* 256 values.
*/
static int sti_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct sti_pwm_chip *pc = to_sti_pwmchip(chip);
struct sti_pwm_compat_data *cdata = pc->cdata;
struct pwm_device *cur = pc->cur;
struct device *dev = pc->dev;
unsigned int prescale = 0, pwmvalx;
int ret;
unsigned int ncfg;
bool period_same = false;
ncfg = sti_pwm_count_configured(chip);
if (ncfg)
period_same = (period_ns == pwm_get_period(cur));
/* Allow configuration changes if one of the
* following conditions satisfy.
* 1. No channels have been configured.
* 2. Only one channel has been configured and the new request
* is for the same channel.
* 3. Only one channel has been configured and the new request is
* for a new channel and period of the new channel is same as
* the current configured period.
* 4. More than one channels are configured and period of the new
* requestis the same as the current period.
*/
if (!ncfg ||
((ncfg == 1) && (pwm->hwpwm == cur->hwpwm)) ||
((ncfg == 1) && (pwm->hwpwm != cur->hwpwm) && period_same) ||
((ncfg > 1) && period_same)) {
/* Enable clock before writing to PWM registers. */
ret = clk_enable(pc->clk);
if (ret)
return ret;
if (!period_same) {
ret = sti_pwm_get_prescale(pc, period_ns, &prescale);
if (ret)
goto clk_dis;
ret =
regmap_field_write(pc->prescale_low,
prescale & PWM_PRESCALE_LOW_MASK);
if (ret)
goto clk_dis;
ret =
regmap_field_write(pc->prescale_high,
(prescale & PWM_PRESCALE_HIGH_MASK) >> 4);
if (ret)
goto clk_dis;
}
/*
* When PWMVal == 0, PWM pulse = 1 local clock cycle.
* When PWMVal == max_pwm_count,
* PWM pulse = (max_pwm_count + 1) local cycles,
* that is continuous pulse: signal never goes low.
*/
pwmvalx = cdata->max_pwm_cnt * duty_ns / period_ns;
ret = regmap_write(pc->regmap, STI_DS_REG(pwm->hwpwm), pwmvalx);
if (ret)
goto clk_dis;
ret = regmap_field_write(pc->pwm_int_en, 0);
pc->cur = pwm;
dev_dbg(dev, "prescale:%u, period:%i, duty:%i, pwmvalx:%u\n",
prescale, period_ns, duty_ns, pwmvalx);
} else {
return -EINVAL;
}
clk_dis:
clk_disable(pc->clk);
return ret;
}
static int sti_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct sti_pwm_chip *pc = to_sti_pwmchip(chip);
struct device *dev = pc->dev;
int ret = 0;
/*
* Since we have a common enable for all PWM channels,
* do not enable if already enabled.
*/
mutex_lock(&pc->sti_pwm_lock);
if (!pc->en_count) {
ret = clk_enable(pc->clk);
if (ret)
goto out;
ret = regmap_field_write(pc->pwm_en, 1);
if (ret) {
dev_err(dev, "failed to enable PWM device:%d\n",
pwm->hwpwm);
goto out;
}
}
pc->en_count++;
out:
mutex_unlock(&pc->sti_pwm_lock);
return ret;
}
static void sti_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct sti_pwm_chip *pc = to_sti_pwmchip(chip);
mutex_lock(&pc->sti_pwm_lock);
if (--pc->en_count) {
mutex_unlock(&pc->sti_pwm_lock);
return;
}
regmap_field_write(pc->pwm_en, 0);
clk_disable(pc->clk);
mutex_unlock(&pc->sti_pwm_lock);
}
static const struct pwm_ops sti_pwm_ops = {
.config = sti_pwm_config,
.enable = sti_pwm_enable,
.disable = sti_pwm_disable,
.owner = THIS_MODULE,
};
static int sti_pwm_probe_dt(struct sti_pwm_chip *pc)
{
struct device *dev = pc->dev;
const struct reg_field *reg_fields;
struct device_node *np = dev->of_node;
struct sti_pwm_compat_data *cdata = pc->cdata;
u32 num_chan;
of_property_read_u32(np, "st,pwm-num-chan", &num_chan);
if (num_chan)
cdata->num_chan = num_chan;
reg_fields = cdata->reg_fields;
pc->prescale_low = devm_regmap_field_alloc(dev, pc->regmap,
reg_fields[PWMCLK_PRESCALE_LOW]);
if (IS_ERR(pc->prescale_low))
return PTR_ERR(pc->prescale_low);
pc->prescale_high = devm_regmap_field_alloc(dev, pc->regmap,
reg_fields[PWMCLK_PRESCALE_HIGH]);
if (IS_ERR(pc->prescale_high))
return PTR_ERR(pc->prescale_high);
pc->pwm_en = devm_regmap_field_alloc(dev, pc->regmap,
reg_fields[PWM_EN]);
if (IS_ERR(pc->pwm_en))
return PTR_ERR(pc->pwm_en);
pc->pwm_int_en = devm_regmap_field_alloc(dev, pc->regmap,
reg_fields[PWM_INT_EN]);
if (IS_ERR(pc->pwm_int_en))
return PTR_ERR(pc->pwm_int_en);
return 0;
}
static const struct regmap_config sti_pwm_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
};
static int sti_pwm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct sti_pwm_compat_data *cdata;
struct sti_pwm_chip *pc;
struct resource *res;
int ret;
pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL);
if (!pc)
return -ENOMEM;
cdata = devm_kzalloc(dev, sizeof(*cdata), GFP_KERNEL);
if (!cdata)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
pc->mmio = devm_ioremap_resource(dev, res);
if (IS_ERR(pc->mmio))
return PTR_ERR(pc->mmio);
pc->regmap = devm_regmap_init_mmio(dev, pc->mmio,
&sti_pwm_regmap_config);
if (IS_ERR(pc->regmap))
return PTR_ERR(pc->regmap);
/*
* Setup PWM data with default values: some values could be replaced
* with specific ones provided from Device Tree.
*/
cdata->reg_fields = &sti_pwm_regfields[0];
cdata->max_prescale = 0xff;
cdata->max_pwm_cnt = 255;
cdata->num_chan = 1;
pc->cdata = cdata;
pc->dev = dev;
pc->en_count = 0;
mutex_init(&pc->sti_pwm_lock);
ret = sti_pwm_probe_dt(pc);
if (ret)
return ret;
pc->clk = of_clk_get_by_name(dev->of_node, "pwm");
if (IS_ERR(pc->clk)) {
dev_err(dev, "failed to get PWM clock\n");
return PTR_ERR(pc->clk);
}
pc->clk_rate = clk_get_rate(pc->clk);
if (!pc->clk_rate) {
dev_err(dev, "failed to get clock rate\n");
return -EINVAL;
}
ret = clk_prepare(pc->clk);
if (ret) {
dev_err(dev, "failed to prepare clock\n");
return ret;
}
pc->chip.dev = dev;
pc->chip.ops = &sti_pwm_ops;
pc->chip.base = -1;
pc->chip.npwm = pc->cdata->num_chan;
pc->chip.can_sleep = true;
ret = pwmchip_add(&pc->chip);
if (ret < 0) {
clk_unprepare(pc->clk);
return ret;
}
platform_set_drvdata(pdev, pc);
return 0;
}
static int sti_pwm_remove(struct platform_device *pdev)
{
struct sti_pwm_chip *pc = platform_get_drvdata(pdev);
unsigned int i;
for (i = 0; i < pc->cdata->num_chan; i++)
pwm_disable(&pc->chip.pwms[i]);
clk_unprepare(pc->clk);
return pwmchip_remove(&pc->chip);
}
static const struct of_device_id sti_pwm_of_match[] = {
{ .compatible = "st,sti-pwm", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sti_pwm_of_match);
static struct platform_driver sti_pwm_driver = {
.driver = {
.name = "sti-pwm",
.of_match_table = sti_pwm_of_match,
},
.probe = sti_pwm_probe,
.remove = sti_pwm_remove,
};
module_platform_driver(sti_pwm_driver);
MODULE_AUTHOR("Ajit Pal Singh <ajitpal.singh@st.com>");
MODULE_DESCRIPTION("STMicroelectronics ST PWM driver");
MODULE_LICENSE("GPL");

251
drivers/pwm/pwm-tegra.c Normal file
View file

@ -0,0 +1,251 @@
/*
* 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) - 1) + 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)
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",
.owner = THIS_MODULE,
.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");

346
drivers/pwm/pwm-tiecap.c Normal file
View file

@ -0,0 +1,346 @@
/*
* 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 "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;
pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
if (!pc)
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_disable(&pdev->dev);
return pwmchip_remove(&pc->chip);
}
#ifdef CONFIG_PM_SLEEP
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);
}
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");

614
drivers/pwm/pwm-tiehrpwm.c Normal file
View file

@ -0,0 +1,614 @@
/*
* 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 "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 inline u16 ehrpwm_read(void __iomem *base, int offset)
{
return readw(base + offset);
}
static inline void ehrpwm_write(void __iomem *base, int offset, unsigned int val)
{
writew(val & 0xFFFF, base + offset);
}
static void ehrpwm_modify(void __iomem *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_enable(pc->tbclk);
if (ret) {
dev_err(chip->dev, "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(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;
pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
if (!pc)
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 = clk_prepare(pc->tbclk);
if (ret < 0) {
dev_err(&pdev->dev, "clk_prepare() failed: %d\n", ret);
return ret;
}
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);
clk_unprepare(pc->tbclk);
return ret;
}
static int ehrpwm_pwm_remove(struct platform_device *pdev)
{
struct ehrpwm_pwm_chip *pc = platform_get_drvdata(pdev);
clk_unprepare(pc->tbclk);
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);
}
#ifdef CONFIG_PM_SLEEP
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);
}
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");

134
drivers/pwm/pwm-tipwmss.c Normal file
View file

@ -0,0 +1,134 @@
/*
* 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)
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 */

348
drivers/pwm/pwm-twl-led.c Normal file
View file

@ -0,0 +1,348 @@
/*
* 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/of.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");

352
drivers/pwm/pwm-twl.c Normal file
View file

@ -0,0 +1,352 @@
/*
* 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/of.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 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");

277
drivers/pwm/pwm-vt8500.c Normal file
View file

@ -0,0 +1,277 @@
/*
* 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)
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");

347
drivers/pwm/sysfs.c Normal file
View file

@ -0,0 +1,347 @@
/*
* A simple sysfs interface for the generic PWM framework
*
* Copyright (C) 2013 H Hartley Sweeten <hsweeten@visionengravers.com>
*
* Based on previous work by Lars Poeschel <poeschel@lemonage.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, 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/device.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/kdev_t.h>
#include <linux/pwm.h>
struct pwm_export {
struct device child;
struct pwm_device *pwm;
};
static struct pwm_export *child_to_pwm_export(struct device *child)
{
return container_of(child, struct pwm_export, child);
}
static struct pwm_device *child_to_pwm_device(struct device *child)
{
struct pwm_export *export = child_to_pwm_export(child);
return export->pwm;
}
static ssize_t pwm_period_show(struct device *child,
struct device_attribute *attr,
char *buf)
{
const struct pwm_device *pwm = child_to_pwm_device(child);
return sprintf(buf, "%u\n", pwm->period);
}
static ssize_t pwm_period_store(struct device *child,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct pwm_device *pwm = child_to_pwm_device(child);
unsigned int val;
int ret;
ret = kstrtouint(buf, 0, &val);
if (ret)
return ret;
ret = pwm_config(pwm, pwm->duty_cycle, val);
return ret ? : size;
}
static ssize_t pwm_duty_cycle_show(struct device *child,
struct device_attribute *attr,
char *buf)
{
const struct pwm_device *pwm = child_to_pwm_device(child);
return sprintf(buf, "%u\n", pwm->duty_cycle);
}
static ssize_t pwm_duty_cycle_store(struct device *child,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct pwm_device *pwm = child_to_pwm_device(child);
unsigned int val;
int ret;
ret = kstrtouint(buf, 0, &val);
if (ret)
return ret;
ret = pwm_config(pwm, val, pwm->period);
return ret ? : size;
}
static ssize_t pwm_enable_show(struct device *child,
struct device_attribute *attr,
char *buf)
{
const struct pwm_device *pwm = child_to_pwm_device(child);
int enabled = test_bit(PWMF_ENABLED, &pwm->flags);
return sprintf(buf, "%d\n", enabled);
}
static ssize_t pwm_enable_store(struct device *child,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct pwm_device *pwm = child_to_pwm_device(child);
int val, ret;
ret = kstrtoint(buf, 0, &val);
if (ret)
return ret;
switch (val) {
case 0:
pwm_disable(pwm);
break;
case 1:
ret = pwm_enable(pwm);
break;
default:
ret = -EINVAL;
break;
}
return ret ? : size;
}
static ssize_t pwm_polarity_show(struct device *child,
struct device_attribute *attr,
char *buf)
{
const struct pwm_device *pwm = child_to_pwm_device(child);
return sprintf(buf, "%s\n", pwm->polarity ? "inversed" : "normal");
}
static ssize_t pwm_polarity_store(struct device *child,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct pwm_device *pwm = child_to_pwm_device(child);
enum pwm_polarity polarity;
int ret;
if (sysfs_streq(buf, "normal"))
polarity = PWM_POLARITY_NORMAL;
else if (sysfs_streq(buf, "inversed"))
polarity = PWM_POLARITY_INVERSED;
else
return -EINVAL;
ret = pwm_set_polarity(pwm, polarity);
return ret ? : size;
}
static DEVICE_ATTR(period, 0644, pwm_period_show, pwm_period_store);
static DEVICE_ATTR(duty_cycle, 0644, pwm_duty_cycle_show, pwm_duty_cycle_store);
static DEVICE_ATTR(enable, 0644, pwm_enable_show, pwm_enable_store);
static DEVICE_ATTR(polarity, 0644, pwm_polarity_show, pwm_polarity_store);
static struct attribute *pwm_attrs[] = {
&dev_attr_period.attr,
&dev_attr_duty_cycle.attr,
&dev_attr_enable.attr,
&dev_attr_polarity.attr,
NULL
};
ATTRIBUTE_GROUPS(pwm);
static void pwm_export_release(struct device *child)
{
struct pwm_export *export = child_to_pwm_export(child);
kfree(export);
}
static int pwm_export_child(struct device *parent, struct pwm_device *pwm)
{
struct pwm_export *export;
int ret;
if (test_and_set_bit(PWMF_EXPORTED, &pwm->flags))
return -EBUSY;
export = kzalloc(sizeof(*export), GFP_KERNEL);
if (!export) {
clear_bit(PWMF_EXPORTED, &pwm->flags);
return -ENOMEM;
}
export->pwm = pwm;
export->child.release = pwm_export_release;
export->child.parent = parent;
export->child.devt = MKDEV(0, 0);
export->child.groups = pwm_groups;
dev_set_name(&export->child, "pwm%u", pwm->hwpwm);
ret = device_register(&export->child);
if (ret) {
clear_bit(PWMF_EXPORTED, &pwm->flags);
kfree(export);
return ret;
}
return 0;
}
static int pwm_unexport_match(struct device *child, void *data)
{
return child_to_pwm_device(child) == data;
}
static int pwm_unexport_child(struct device *parent, struct pwm_device *pwm)
{
struct device *child;
if (!test_and_clear_bit(PWMF_EXPORTED, &pwm->flags))
return -ENODEV;
child = device_find_child(parent, pwm, pwm_unexport_match);
if (!child)
return -ENODEV;
/* for device_find_child() */
put_device(child);
device_unregister(child);
pwm_put(pwm);
return 0;
}
static ssize_t pwm_export_store(struct device *parent,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct pwm_chip *chip = dev_get_drvdata(parent);
struct pwm_device *pwm;
unsigned int hwpwm;
int ret;
ret = kstrtouint(buf, 0, &hwpwm);
if (ret < 0)
return ret;
if (hwpwm >= chip->npwm)
return -ENODEV;
pwm = pwm_request_from_chip(chip, hwpwm, "sysfs");
if (IS_ERR(pwm))
return PTR_ERR(pwm);
ret = pwm_export_child(parent, pwm);
if (ret < 0)
pwm_put(pwm);
return ret ? : len;
}
static DEVICE_ATTR(export, 0200, NULL, pwm_export_store);
static ssize_t pwm_unexport_store(struct device *parent,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct pwm_chip *chip = dev_get_drvdata(parent);
unsigned int hwpwm;
int ret;
ret = kstrtouint(buf, 0, &hwpwm);
if (ret < 0)
return ret;
if (hwpwm >= chip->npwm)
return -ENODEV;
ret = pwm_unexport_child(parent, &chip->pwms[hwpwm]);
return ret ? : len;
}
static DEVICE_ATTR(unexport, 0200, NULL, pwm_unexport_store);
static ssize_t npwm_show(struct device *parent, struct device_attribute *attr,
char *buf)
{
const struct pwm_chip *chip = dev_get_drvdata(parent);
return sprintf(buf, "%u\n", chip->npwm);
}
static DEVICE_ATTR_RO(npwm);
static struct attribute *pwm_chip_attrs[] = {
&dev_attr_export.attr,
&dev_attr_unexport.attr,
&dev_attr_npwm.attr,
NULL,
};
ATTRIBUTE_GROUPS(pwm_chip);
static struct class pwm_class = {
.name = "pwm",
.owner = THIS_MODULE,
.dev_groups = pwm_chip_groups,
};
static int pwmchip_sysfs_match(struct device *parent, const void *data)
{
return dev_get_drvdata(parent) == data;
}
void pwmchip_sysfs_export(struct pwm_chip *chip)
{
struct device *parent;
/*
* If device_create() fails the pwm_chip is still usable by
* the kernel its just not exported.
*/
parent = device_create(&pwm_class, chip->dev, MKDEV(0, 0), chip,
"pwmchip%d", chip->base);
if (IS_ERR(parent)) {
dev_warn(chip->dev,
"device_create failed for pwm_chip sysfs export\n");
}
}
void pwmchip_sysfs_unexport(struct pwm_chip *chip)
{
struct device *parent;
parent = class_find_device(&pwm_class, NULL, chip,
pwmchip_sysfs_match);
if (parent) {
/* for class_find_device() */
put_device(parent);
device_unregister(parent);
}
}
static int __init pwm_sysfs_init(void)
{
return class_register(&pwm_class);
}
subsys_initcall(pwm_sysfs_init);