The legacy HAVE_PWM Kconfig symbol is finally being retired. Thanks a lot to Sascha Hauer for doing that. Three new drivers are added: Freescale FTM, Cirrus Logic CLPS711X and Intel Low Power Subsystem. An assortment of fixes and cleanups rounds things off for this release cycle. -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.22 (GNU/Linux) iQIcBAABAgAGBQJTPmXoAAoJEN0jrNd/PrOhSacQAKNpqWHpFdFuhqpO6dvmqYj3 dvf6EDMnNaOS+TjbCvwP5awAiBhTbJRaTclP1lXXXOnzHvzeeYWhS2ESp4Yl8mRx GRHj5OxmquaVPY5HN+6guVyCrgq4R2sxPU1P2VoPhhomhvP2VuEBbD/ddudC3e2k /e9BuBhUB9eaur6d+vKX7Bnz09wf+ASobgIisjyyqSYysDgE82BAanX/knnLIyQL RKCsz75w14rIxU/f8EML8EMnWiGINYpP+M/NGtPvcNBBOX9DkdzBvSvcbm+gS6ma g2P+zsJgxhUpvvmzhqUumADUU8BWo/P1Y/6FQGRku6EmmJQQspTvDvOs1jCauouC 5vUA41Jwh+4+AKeNWN28tDlh9i5kKYdzYP5SeRcM9mW1SI7AIFmg62lxdus7ZnBB e8UFd26kp/hZxXPdDVHtQi9y5Z5kn4axutVpbISuW5P9z1HF9bFOVHKQVlk7D6uz EqqiYLdW/MxrmBq+v35biwx6afk3zJ8Qas/MmVIVTcLcLDTFLPEm4EawwcRZo8F3 Jh4p4IHxjEgLYcwVBNOe4ZBJg10fM1gmh18dDTyri759HE1mpi4/DwTGcv3iK4AU njv4Q+qBq9QkY2ktw3qCkTDcwiM9jm+FHfdyKXeR5+CjfOf61/CF+N1jBQ8ZMrb7 XIRHle+mvL/RYpPDML/P =pbF+ -----END PGP SIGNATURE----- Merge tag 'pwm/for-3.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm Pull pwm changes from Thierry Reding: "The legacy HAVE_PWM Kconfig symbol is finally being retired. Thanks a lot to Sascha Hauer for doing that. Three new drivers are added: Freescale FTM, Cirrus Logic CLPS711X and Intel Low Power Subsystem. An assortment of fixes and cleanups rounds things off for this release cycle" * tag 'pwm/for-3.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm: pwm: pxa: Constify OF match table pwm: pxa: Fix typo "pwm" -> "PWM" Revert "pwm: pxa: Use of_match_ptr()" pwm: add support for Intel Low Power Subsystem PWM pwm: Add CLPS711X PWM support pwm: atmel: correct CDTY calculation pwm: atmel: Fix polarity handling Documentation: Add device tree bindings for Freescale FTM PWM. pwm: Add Freescale FTM PWM driver support pwm: pxa: Use of_match_ptr() pwm: samsung: Use SIMPLE_DEV_PM_OPS macro pwm: renesas-tpu: Add dependency on HAS_IOMEM pwm: Remove obsolete HAVE_PWM Kconfig symboltirimbino
commit
9712d3c377
@ -0,0 +1,16 @@ |
||||
* Cirris Logic CLPS711X PWM controller |
||||
|
||||
Required properties: |
||||
- compatible: Shall contain "cirrus,clps711x-pwm". |
||||
- reg: Physical base address and length of the controller's registers. |
||||
- clocks: phandle + clock specifier pair of the PWM reference clock. |
||||
- #pwm-cells: Should be 1. The cell specifies the index of the channel. |
||||
|
||||
Example: |
||||
pwm: pwm@80000400 { |
||||
compatible = "cirrus,ep7312-pwm", |
||||
"cirrus,clps711x-pwm"; |
||||
reg = <0x80000400 0x4>; |
||||
clocks = <&clks 8>; |
||||
#pwm-cells = <1>; |
||||
}; |
@ -0,0 +1,35 @@ |
||||
Freescale FlexTimer Module (FTM) PWM controller |
||||
|
||||
Required properties: |
||||
- compatible: Should be "fsl,vf610-ftm-pwm". |
||||
- reg: Physical base address and length of the controller's registers |
||||
- #pwm-cells: Should be 3. See pwm.txt in this directory for a description of |
||||
the cells format. |
||||
- clock-names: Should include the following module clock source entries: |
||||
"ftm_sys" (module clock, also can be used as counter clock), |
||||
"ftm_ext" (external counter clock), |
||||
"ftm_fix" (fixed counter clock), |
||||
"ftm_cnt_clk_en" (external and fixed counter clock enable/disable). |
||||
- clocks: Must contain a phandle and clock specifier for each entry in |
||||
clock-names, please see clock/clock-bindings.txt for details of the property |
||||
values. |
||||
- pinctrl-names: Must contain a "default" entry. |
||||
- pinctrl-NNN: One property must exist for each entry in pinctrl-names. |
||||
See pinctrl/pinctrl-bindings.txt for details of the property values. |
||||
|
||||
|
||||
Example: |
||||
|
||||
pwm0: pwm@40038000 { |
||||
compatible = "fsl,vf610-ftm-pwm"; |
||||
reg = <0x40038000 0x1000>; |
||||
#pwm-cells = <3>; |
||||
clock-names = "ftm_sys", "ftm_ext", |
||||
"ftm_fix", "ftm_cnt_clk_en"; |
||||
clocks = <&clks VF610_CLK_FTM0>, |
||||
<&clks VF610_CLK_FTM0_EXT_SEL>, |
||||
<&clks VF610_CLK_FTM0_FIX_SEL>, |
||||
<&clks VF610_CLK_FTM0_EXT_FIX_EN>; |
||||
pinctrl-names = "default"; |
||||
pinctrl-0 = <&pinctrl_pwm0_1>; |
||||
}; |
@ -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"); |
@ -0,0 +1,495 @@ |
||||
/*
|
||||
* 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/slab.h> |
||||
|
||||
#define FTM_SC 0x00 |
||||
#define FTM_SC_CLK_MASK 0x3 |
||||
#define FTM_SC_CLK_SHIFT 3 |
||||
#define FTM_SC_CLK(c) (((c) + 1) << FTM_SC_CLK_SHIFT) |
||||
#define FTM_SC_PS_MASK 0x7 |
||||
#define FTM_SC_PS_SHIFT 0 |
||||
|
||||
#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; |
||||
|
||||
void __iomem *base; |
||||
|
||||
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 val, duty; |
||||
|
||||
val = readl(fpc->base + FTM_MOD); |
||||
duty = 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 val, 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; |
||||
} |
||||
|
||||
val = readl(fpc->base + FTM_SC); |
||||
val &= ~(FTM_SC_PS_MASK << FTM_SC_PS_SHIFT); |
||||
val |= fpc->clk_ps; |
||||
writel(val, fpc->base + FTM_SC); |
||||
writel(period - 1, fpc->base + FTM_MOD); |
||||
|
||||
fpc->period_ns = period_ns; |
||||
} |
||||
|
||||
mutex_unlock(&fpc->lock); |
||||
|
||||
duty = fsl_pwm_calculate_duty(fpc, period_ns, duty_ns); |
||||
|
||||
writel(FTM_CSC_MSB | FTM_CSC_ELSB, fpc->base + FTM_CSC(pwm->hwpwm)); |
||||
writel(duty, fpc->base + FTM_CV(pwm->hwpwm)); |
||||
|
||||
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; |
||||
|
||||
val = readl(fpc->base + FTM_POL); |
||||
|
||||
if (polarity == PWM_POLARITY_INVERSED) |
||||
val |= BIT(pwm->hwpwm); |
||||
else |
||||
val &= ~BIT(pwm->hwpwm); |
||||
|
||||
writel(val, fpc->base + FTM_POL); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int fsl_counter_clock_enable(struct fsl_pwm_chip *fpc) |
||||
{ |
||||
u32 val; |
||||
int ret; |
||||
|
||||
if (fpc->use_count != 0) |
||||
return 0; |
||||
|
||||
/* select counter clock source */ |
||||
val = readl(fpc->base + FTM_SC); |
||||
val &= ~(FTM_SC_CLK_MASK << FTM_SC_CLK_SHIFT); |
||||
val |= FTM_SC_CLK(fpc->cnt_select); |
||||
writel(val, fpc->base + FTM_SC); |
||||
|
||||
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); |
||||
u32 val; |
||||
int ret; |
||||
|
||||
mutex_lock(&fpc->lock); |
||||
val = readl(fpc->base + FTM_OUTMASK); |
||||
val &= ~BIT(pwm->hwpwm); |
||||
writel(val, fpc->base + FTM_OUTMASK); |
||||
|
||||
ret = fsl_counter_clock_enable(fpc); |
||||
mutex_unlock(&fpc->lock); |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
static void fsl_counter_clock_disable(struct fsl_pwm_chip *fpc) |
||||
{ |
||||
u32 val; |
||||
|
||||
/*
|
||||
* 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 */ |
||||
val = readl(fpc->base + FTM_SC); |
||||
val &= ~(FTM_SC_CLK_MASK << FTM_SC_CLK_SHIFT); |
||||
writel(val, fpc->base + FTM_SC); |
||||
|
||||
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); |
||||
val = readl(fpc->base + FTM_OUTMASK); |
||||
val |= BIT(pwm->hwpwm); |
||||
writel(val, fpc->base + FTM_OUTMASK); |
||||
|
||||
fsl_counter_clock_disable(fpc); |
||||
|
||||
val = readl(fpc->base + FTM_OUTMASK); |
||||
|
||||
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; |
||||
|
||||
writel(0x00, fpc->base + FTM_CNTIN); |
||||
writel(0x00, fpc->base + FTM_OUTINIT); |
||||
writel(0xFF, fpc->base + FTM_OUTMASK); |
||||
|
||||
clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_SYS]); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int fsl_pwm_probe(struct platform_device *pdev) |
||||
{ |
||||
struct fsl_pwm_chip *fpc; |
||||
struct resource *res; |
||||
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); |
||||
fpc->base = devm_ioremap_resource(&pdev->dev, res); |
||||
if (IS_ERR(fpc->base)) |
||||
return PTR_ERR(fpc->base); |
||||
|
||||
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; |
||||
|
||||
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"); |
@ -0,0 +1,183 @@ |
||||
/*
|
||||
* 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> |
||||
* |
||||
* 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/clk.h> |
||||
#include <linux/device.h> |
||||
#include <linux/kernel.h> |
||||
#include <linux/module.h> |
||||
#include <linux/pwm.h> |
||||
#include <linux/platform_device.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; |
||||
struct clk *clk; |
||||
}; |
||||
|
||||
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 = clk_get_rate(lpwm->clk); |
||||
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; |
||||
int ret; |
||||
|
||||
ret = clk_prepare_enable(lpwm->clk); |
||||
if (ret) |
||||
return ret; |
||||
|
||||
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); |
||||
|
||||
clk_disable_unprepare(lpwm->clk); |
||||
} |
||||
|
||||
static const struct pwm_ops pwm_lpss_ops = { |
||||
.config = pwm_lpss_config, |
||||
.enable = pwm_lpss_enable, |
||||
.disable = pwm_lpss_disable, |
||||
.owner = THIS_MODULE, |
||||
}; |
||||
|
||||
static const struct acpi_device_id pwm_lpss_acpi_match[] = { |
||||
{ "80860F09", 0 }, |
||||
{ }, |
||||
}; |
||||
MODULE_DEVICE_TABLE(acpi, pwm_lpss_acpi_match); |
||||
|
||||
static int pwm_lpss_probe(struct platform_device *pdev) |
||||
{ |
||||
struct pwm_lpss_chip *lpwm; |
||||
struct resource *r; |
||||
int ret; |
||||
|
||||
lpwm = devm_kzalloc(&pdev->dev, sizeof(*lpwm), GFP_KERNEL); |
||||
if (!lpwm) |
||||
return -ENOMEM; |
||||
|
||||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
||||
|
||||
lpwm->regs = devm_ioremap_resource(&pdev->dev, r); |
||||
if (IS_ERR(lpwm->regs)) |
||||
return PTR_ERR(lpwm->regs); |
||||
|
||||
lpwm->clk = devm_clk_get(&pdev->dev, NULL); |
||||
if (IS_ERR(lpwm->clk)) { |
||||
dev_err(&pdev->dev, "failed to get PWM clock\n"); |
||||
return PTR_ERR(lpwm->clk); |
||||
} |
||||
|
||||
lpwm->chip.dev = &pdev->dev; |
||||
lpwm->chip.ops = &pwm_lpss_ops; |
||||
lpwm->chip.base = -1; |
||||
lpwm->chip.npwm = 1; |
||||
|
||||
ret = pwmchip_add(&lpwm->chip); |
||||
if (ret) { |
||||
dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); |
||||
return ret; |
||||
} |
||||
|
||||
platform_set_drvdata(pdev, lpwm); |
||||
return 0; |
||||
} |
||||
|
||||
static int pwm_lpss_remove(struct platform_device *pdev) |
||||
{ |
||||
struct pwm_lpss_chip *lpwm = platform_get_drvdata(pdev); |
||||
u32 ctrl; |
||||
|
||||
ctrl = readl(lpwm->regs + PWM); |
||||
writel(ctrl & ~PWM_ENABLE, lpwm->regs + PWM); |
||||
|
||||
return pwmchip_remove(&lpwm->chip); |
||||
} |
||||
|
||||
static struct platform_driver pwm_lpss_driver = { |
||||
.driver = { |
||||
.name = "pwm-lpss", |
||||
.acpi_match_table = pwm_lpss_acpi_match, |
||||
}, |
||||
.probe = pwm_lpss_probe, |
||||
.remove = pwm_lpss_remove, |
||||
}; |
||||
module_platform_driver(pwm_lpss_driver); |
||||
|
||||
MODULE_DESCRIPTION("PWM driver for Intel LPSS"); |
||||
MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>"); |
||||
MODULE_LICENSE("GPL v2"); |
||||
MODULE_ALIAS("platform:pwm-lpss"); |
Loading…
Reference in new issue