Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0b4954c4 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'pwm/for-3.19-rc1' of...

Merge tag 'pwm/for-3.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm

Pull pwm updates from Thierry Reding:
 "There are two new drivers, one for the BCM2835 (Raspberry Pi) and one
  used in conjunction with the LCD controller on various Atmel SoCs.
  The Samsung PWM driver can now be built for 64-bit ARM (Exynos7).

  A couple of fixes have been applied to the FTM PWM driver and system
  sleep support was added"

* tag 'pwm/for-3.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm:
  pwm: atmel-hlcdc: add at91sam9x5 and sama5d3 errata handling
  pwm: ftm: Add Power Management support for FTM PWM
  pwm: ftm: Add regmap rbtree type cache support
  pwm: ftm: Correctly track usage count
  pwm: samsung: Allow Samsung PWM driver to be enabled on Exynos7
  pwm: add DT bindings documentation for atmel-hlcdc-pwm driver
  pwm: add support for atmel-hlcdc-pwm device
  pwm: Add BCM2835 PWM driver
parents d797da41 39e046f2
Loading
Loading
Loading
Loading
+29 −0
Original line number Diff line number Diff line
Device-Tree bindings for Atmel's HLCDC (High-end LCD Controller) PWM driver

The Atmel HLCDC PWM is subdevice of the HLCDC MFD device.
See ../mfd/atmel-hlcdc.txt for more details.

Required properties:
 - compatible: value should be one of the following:
   "atmel,hlcdc-pwm"
 - pinctr-names: the pin control state names. Should contain "default".
 - pinctrl-0: should contain the pinctrl states described by pinctrl
   default.
 - #pwm-cells: should be set to 3. This PWM chip use the default 3 cells
   bindings defined in pwm.txt in this directory.

Example:

	hlcdc: hlcdc@f0030000 {
		compatible = "atmel,sama5d3-hlcdc";
		reg = <0xf0030000 0x2000>;
		clocks = <&lcdc_clk>, <&lcdck>, <&clk32k>;
		clock-names = "periph_clk","sys_clk", "slow_clk";

		hlcdc_pwm: hlcdc-pwm {
			compatible = "atmel,hlcdc-pwm";
			pinctrl-names = "default";
			pinctrl-0 = <&pinctrl_lcd_pwm>;
			#pwm-cells = <3>;
		};
	};
+30 −0
Original line number Diff line number Diff line
BCM2835 PWM controller (Raspberry Pi controller)

Required properties:
- compatible: should be "brcm,bcm2835-pwm"
- reg: physical base address and length of the controller's registers
- clock: This clock defines the base clock frequency of the PWM hardware
  system, the period and the duty_cycle of the PWM signal is a multiple of
  the base period.
- #pwm-cells: Should be 2. See pwm.txt in this directory for a description of
  the cells format.

Examples:

pwm@2020c000 {
	compatible = "brcm,bcm2835-pwm";
	reg = <0x2020c000 0x28>;
	clocks = <&clk_pwm>;
	#pwm-cells = <2>;
};

clocks {
	....
		clk_pwm: pwm {
			compatible = "fixed-clock";
			reg = <3>;
			#clock-cells = <0>;
			clock-frequency = <9200000>;
		};
	....
};
+21 −1
Original line number Diff line number Diff line
@@ -50,6 +50,17 @@ config PWM_ATMEL
	  To compile this driver as a module, choose M here: the module
	  will be called pwm-atmel.

config PWM_ATMEL_HLCDC_PWM
	tristate "Atmel HLCDC PWM support"
	depends on MFD_ATMEL_HLCDC
	help
	  Generic PWM framework driver for the PWM output of the HLCDC
	  (Atmel High-end LCD Controller). This PWM output is mainly used
	  to control the LCD backlight.

	  To compile this driver as a module, choose M here: the module
	  will be called pwm-atmel-hlcdc.

config PWM_ATMEL_TCB
	tristate "Atmel TC Block PWM support"
	depends on ATMEL_TCLIB && OF
@@ -71,6 +82,15 @@ config PWM_BCM_KONA
	  To compile this driver as a module, choose M here: the module
	  will be called pwm-bcm-kona.

config PWM_BCM2835
	tristate "BCM2835 PWM support"
	depends on ARCH_BCM2835
	help
	  PWM framework driver for BCM2835 controller (Raspberry Pi)

	  To compile this driver as a module, choose M here: the module
	  will be called pwm-bcm2835.

config PWM_BFIN
	tristate "Blackfin PWM support"
	depends on BFIN_GPTIMERS
@@ -235,7 +255,7 @@ config PWM_ROCKCHIP

config PWM_SAMSUNG
	tristate "Samsung PWM support"
	depends on PLAT_SAMSUNG
	depends on PLAT_SAMSUNG || ARCH_EXYNOS
	help
	  Generic PWM framework driver for Samsung.

+2 −0
Original line number Diff line number Diff line
@@ -2,8 +2,10 @@ 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_HLCDC_PWM)	+= pwm-atmel-hlcdc.o
obj-$(CONFIG_PWM_ATMEL_TCB)	+= pwm-atmel-tcb.o
obj-$(CONFIG_PWM_BCM_KONA)	+= pwm-bcm-kona.o
obj-$(CONFIG_PWM_BCM2835)	+= pwm-bcm2835.o
obj-$(CONFIG_PWM_BFIN)		+= pwm-bfin.o
obj-$(CONFIG_PWM_CLPS711X)	+= pwm-clps711x.o
obj-$(CONFIG_PWM_EP93XX)	+= pwm-ep93xx.o
+299 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 Free Electrons
 * Copyright (C) 2014 Atmel
 *
 * Author: Boris BREZILLON <boris.brezillon@free-electrons.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/clk.h>
#include <linux/delay.h>
#include <linux/mfd/atmel-hlcdc.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/regmap.h>

#define ATMEL_HLCDC_PWMCVAL_MASK	GENMASK(15, 8)
#define ATMEL_HLCDC_PWMCVAL(x)		(((x) << 8) & ATMEL_HLCDC_PWMCVAL_MASK)
#define ATMEL_HLCDC_PWMPOL		BIT(4)
#define ATMEL_HLCDC_PWMPS_MASK		GENMASK(2, 0)
#define ATMEL_HLCDC_PWMPS_MAX		0x6
#define ATMEL_HLCDC_PWMPS(x)		((x) & ATMEL_HLCDC_PWMPS_MASK)

struct atmel_hlcdc_pwm_errata {
	bool slow_clk_erratum;
	bool div1_clk_erratum;
};

struct atmel_hlcdc_pwm {
	struct pwm_chip chip;
	struct atmel_hlcdc *hlcdc;
	struct clk *cur_clk;
	const struct atmel_hlcdc_pwm_errata *errata;
};

static inline struct atmel_hlcdc_pwm *to_atmel_hlcdc_pwm(struct pwm_chip *chip)
{
	return container_of(chip, struct atmel_hlcdc_pwm, chip);
}

static int atmel_hlcdc_pwm_config(struct pwm_chip *c,
				  struct pwm_device *pwm,
				  int duty_ns, int period_ns)
{
	struct atmel_hlcdc_pwm *chip = to_atmel_hlcdc_pwm(c);
	struct atmel_hlcdc *hlcdc = chip->hlcdc;
	struct clk *new_clk = hlcdc->slow_clk;
	u64 pwmcval = duty_ns * 256;
	unsigned long clk_freq;
	u64 clk_period_ns;
	u32 pwmcfg;
	int pres;

	if (!chip->errata || !chip->errata->slow_clk_erratum) {
		clk_freq = clk_get_rate(new_clk);
		clk_period_ns = (u64)NSEC_PER_SEC * 256;
		do_div(clk_period_ns, clk_freq);
	}

	/* Errata: cannot use slow clk on some IP revisions */
	if ((chip->errata && chip->errata->slow_clk_erratum) ||
	    clk_period_ns > period_ns) {
		new_clk = hlcdc->sys_clk;
		clk_freq = clk_get_rate(new_clk);
		clk_period_ns = (u64)NSEC_PER_SEC * 256;
		do_div(clk_period_ns, clk_freq);
	}

	for (pres = 0; pres <= ATMEL_HLCDC_PWMPS_MAX; pres++) {
		/* Errata: cannot divide by 1 on some IP revisions */
		if (!pres && chip->errata && chip->errata->div1_clk_erratum)
			continue;

		if ((clk_period_ns << pres) >= period_ns)
			break;
	}

	if (pres > ATMEL_HLCDC_PWMPS_MAX)
		return -EINVAL;

	pwmcfg = ATMEL_HLCDC_PWMPS(pres);

	if (new_clk != chip->cur_clk) {
		u32 gencfg = 0;
		int ret;

		ret = clk_prepare_enable(new_clk);
		if (ret)
			return ret;

		clk_disable_unprepare(chip->cur_clk);
		chip->cur_clk = new_clk;

		if (new_clk == hlcdc->sys_clk)
			gencfg = ATMEL_HLCDC_CLKPWMSEL;

		ret = regmap_update_bits(hlcdc->regmap, ATMEL_HLCDC_CFG(0),
					 ATMEL_HLCDC_CLKPWMSEL, gencfg);
		if (ret)
			return ret;
	}

	do_div(pwmcval, period_ns);

	/*
	 * The PWM duty cycle is configurable from 0/256 to 255/256 of the
	 * period cycle. Hence we can't set a duty cycle occupying the
	 * whole period cycle if we're asked to.
	 * Set it to 255 if pwmcval is greater than 256.
	 */
	if (pwmcval > 255)
		pwmcval = 255;

	pwmcfg |= ATMEL_HLCDC_PWMCVAL(pwmcval);

	return regmap_update_bits(hlcdc->regmap, ATMEL_HLCDC_CFG(6),
				  ATMEL_HLCDC_PWMCVAL_MASK |
				  ATMEL_HLCDC_PWMPS_MASK,
				  pwmcfg);
}

static int atmel_hlcdc_pwm_set_polarity(struct pwm_chip *c,
					struct pwm_device *pwm,
					enum pwm_polarity polarity)
{
	struct atmel_hlcdc_pwm *chip = to_atmel_hlcdc_pwm(c);
	struct atmel_hlcdc *hlcdc = chip->hlcdc;
	u32 cfg = 0;

	if (polarity == PWM_POLARITY_NORMAL)
		cfg = ATMEL_HLCDC_PWMPOL;

	return regmap_update_bits(hlcdc->regmap, ATMEL_HLCDC_CFG(6),
				  ATMEL_HLCDC_PWMPOL, cfg);
}

static int atmel_hlcdc_pwm_enable(struct pwm_chip *c, struct pwm_device *pwm)
{
	struct atmel_hlcdc_pwm *chip = to_atmel_hlcdc_pwm(c);
	struct atmel_hlcdc *hlcdc = chip->hlcdc;
	u32 status;
	int ret;

	ret = regmap_write(hlcdc->regmap, ATMEL_HLCDC_EN, ATMEL_HLCDC_PWM);
	if (ret)
		return ret;

	while (true) {
		ret = regmap_read(hlcdc->regmap, ATMEL_HLCDC_SR, &status);
		if (ret)
			return ret;

		if ((status & ATMEL_HLCDC_PWM) != 0)
			break;

		usleep_range(1, 10);
	}

	return 0;
}

static void atmel_hlcdc_pwm_disable(struct pwm_chip *c,
				    struct pwm_device *pwm)
{
	struct atmel_hlcdc_pwm *chip = to_atmel_hlcdc_pwm(c);
	struct atmel_hlcdc *hlcdc = chip->hlcdc;
	u32 status;
	int ret;

	ret = regmap_write(hlcdc->regmap, ATMEL_HLCDC_DIS, ATMEL_HLCDC_PWM);
	if (ret)
		return;

	while (true) {
		ret = regmap_read(hlcdc->regmap, ATMEL_HLCDC_SR, &status);
		if (ret)
			return;

		if ((status & ATMEL_HLCDC_PWM) == 0)
			break;

		usleep_range(1, 10);
	}
}

static const struct pwm_ops atmel_hlcdc_pwm_ops = {
	.config = atmel_hlcdc_pwm_config,
	.set_polarity = atmel_hlcdc_pwm_set_polarity,
	.enable = atmel_hlcdc_pwm_enable,
	.disable = atmel_hlcdc_pwm_disable,
	.owner = THIS_MODULE,
};

static const struct atmel_hlcdc_pwm_errata atmel_hlcdc_pwm_at91sam9x5_errata = {
	.slow_clk_erratum = true,
};

static const struct atmel_hlcdc_pwm_errata atmel_hlcdc_pwm_sama5d3_errata = {
	.div1_clk_erratum = true,
};

static const struct of_device_id atmel_hlcdc_dt_ids[] = {
	{
		.compatible = "atmel,at91sam9x5-hlcdc",
		.data = &atmel_hlcdc_pwm_at91sam9x5_errata,
	},
	{
		.compatible = "atmel,sama5d3-hlcdc",
		.data = &atmel_hlcdc_pwm_sama5d3_errata,
	},
	{ /* sentinel */ },
};

static int atmel_hlcdc_pwm_probe(struct platform_device *pdev)
{
	const struct of_device_id *match;
	struct device *dev = &pdev->dev;
	struct atmel_hlcdc_pwm *chip;
	struct atmel_hlcdc *hlcdc;
	int ret;

	hlcdc = dev_get_drvdata(dev->parent);

	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
	if (!chip)
		return -ENOMEM;

	ret = clk_prepare_enable(hlcdc->periph_clk);
	if (ret)
		return ret;

	match = of_match_node(atmel_hlcdc_dt_ids, dev->parent->of_node);
	if (match)
		chip->errata = match->data;

	chip->hlcdc = hlcdc;
	chip->chip.ops = &atmel_hlcdc_pwm_ops;
	chip->chip.dev = dev;
	chip->chip.base = -1;
	chip->chip.npwm = 1;
	chip->chip.of_xlate = of_pwm_xlate_with_flags;
	chip->chip.of_pwm_n_cells = 3;
	chip->chip.can_sleep = 1;

	ret = pwmchip_add(&chip->chip);
	if (ret) {
		clk_disable_unprepare(hlcdc->periph_clk);
		return ret;
	}

	platform_set_drvdata(pdev, chip);

	return 0;
}

static int atmel_hlcdc_pwm_remove(struct platform_device *pdev)
{
	struct atmel_hlcdc_pwm *chip = platform_get_drvdata(pdev);
	int ret;

	ret = pwmchip_remove(&chip->chip);
	if (ret)
		return ret;

	clk_disable_unprepare(chip->hlcdc->periph_clk);

	return 0;
}

static const struct of_device_id atmel_hlcdc_pwm_dt_ids[] = {
	{ .compatible = "atmel,hlcdc-pwm" },
	{ /* sentinel */ },
};

static struct platform_driver atmel_hlcdc_pwm_driver = {
	.driver = {
		.name = "atmel-hlcdc-pwm",
		.of_match_table = atmel_hlcdc_pwm_dt_ids,
	},
	.probe = atmel_hlcdc_pwm_probe,
	.remove = atmel_hlcdc_pwm_remove,
};
module_platform_driver(atmel_hlcdc_pwm_driver);

MODULE_ALIAS("platform:atmel-hlcdc-pwm");
MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>");
MODULE_DESCRIPTION("Atmel HLCDC PWM driver");
MODULE_LICENSE("GPL v2");
Loading