mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 01:08:03 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
332
drivers/pwm/Kconfig
Normal file
332
drivers/pwm/Kconfig
Normal 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
33
drivers/pwm/Makefile
Normal 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
895
drivers/pwm/core.c
Normal 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
144
drivers/pwm/pwm-ab8500.c
Normal 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
449
drivers/pwm/pwm-atmel-tcb.c
Normal 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
399
drivers/pwm/pwm-atmel.c
Normal 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
318
drivers/pwm/pwm-bcm-kona.c
Normal 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
160
drivers/pwm/pwm-bfin.c
Normal 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
176
drivers/pwm/pwm-clps711x.c
Normal 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
230
drivers/pwm/pwm-ep93xx.c
Normal 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
500
drivers/pwm/pwm-fsl-ftm.c
Normal 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
349
drivers/pwm/pwm-imx.c
Normal 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
207
drivers/pwm/pwm-jz4740.c
Normal 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
317
drivers/pwm/pwm-lp3943.c
Normal 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
182
drivers/pwm/pwm-lpc32xx.c
Normal 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");
|
64
drivers/pwm/pwm-lpss-pci.c
Normal file
64
drivers/pwm/pwm-lpss-pci.c
Normal 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");
|
68
drivers/pwm/pwm-lpss-platform.c
Normal file
68
drivers/pwm/pwm-lpss-platform.c
Normal 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
163
drivers/pwm/pwm-lpss.c
Normal 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
32
drivers/pwm/pwm-lpss.h
Normal 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
203
drivers/pwm/pwm-mxs.c
Normal 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
300
drivers/pwm/pwm-pca9685.c
Normal 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
156
drivers/pwm/pwm-puv3.c
Normal 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
238
drivers/pwm/pwm-pxa.c
Normal 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");
|
481
drivers/pwm/pwm-renesas-tpu.c
Normal file
481
drivers/pwm/pwm-renesas-tpu.c
Normal 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
303
drivers/pwm/pwm-rockchip.c
Normal 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
849
drivers/pwm/pwm-samsung.c
Executable 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
267
drivers/pwm/pwm-spear.c
Normal 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
418
drivers/pwm/pwm-sti.c
Normal 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
251
drivers/pwm/pwm-tegra.c
Normal 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
346
drivers/pwm/pwm-tiecap.c
Normal 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
614
drivers/pwm/pwm-tiehrpwm.c
Normal 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
134
drivers/pwm/pwm-tipwmss.c
Normal 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
39
drivers/pwm/pwm-tipwmss.h
Normal 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
348
drivers/pwm/pwm-twl-led.c
Normal 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
352
drivers/pwm/pwm-twl.c
Normal 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
277
drivers/pwm/pwm-vt8500.c
Normal 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
347
drivers/pwm/sysfs.c
Normal 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);
|
Loading…
Add table
Add a link
Reference in a new issue