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

Commit a2308698 authored by H Hartley Sweeten's avatar H Hartley Sweeten Committed by Thierry Reding
Browse files

pwm: add ep93xx PWM support



Remove the non-standard EP93xx PWM driver in drivers/misc and add
a new driver for the PWM controllers on the EP93xx platform based
on the PWM framework.

These PWM controllers each support 1 PWM channel with programmable
duty cycle, frequency, and polarity inversion.

Signed-off-by: default avatarH Hartley Sweeten <hsweeten@visionengravers.com>
Cc: Ryan Mallon <rmallon@gmail.com>
Acked-by: default avatarArnd Bergmann <arnd@arndb.de>
Acked-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: default avatarThierry Reding <thierry.reding@gmail.com>
parent 8f43e18e
Loading
Loading
Loading
Loading
+0 −13
Original line number Diff line number Diff line
@@ -381,19 +381,6 @@ config HMC6352
	  This driver provides support for the Honeywell HMC6352 compass,
	  providing configuration and heading data via sysfs.

config EP93XX_PWM
	tristate "EP93xx PWM support"
	depends on ARCH_EP93XX
	help
	  This option enables device driver support for the PWM channels
	  on the Cirrus EP93xx processors.  The EP9307 chip only has one
	  PWM channel all the others have two, the second channel is an
	  alternate function of the EGPIO14 pin.  A sysfs interface is
	  provided to control the PWM channels.

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

config DS1682
	tristate "Dallas DS1682 Total Elapsed Time Recorder with Alarm"
	depends on I2C
+0 −1
Original line number Diff line number Diff line
@@ -33,7 +33,6 @@ obj-$(CONFIG_APDS9802ALS) += apds9802als.o
obj-$(CONFIG_ISL29003)		+= isl29003.o
obj-$(CONFIG_ISL29020)		+= isl29020.o
obj-$(CONFIG_SENSORS_TSL2550)	+= tsl2550.o
obj-$(CONFIG_EP93XX_PWM)	+= ep93xx_pwm.o
obj-$(CONFIG_DS1682)		+= ds1682.o
obj-$(CONFIG_TI_DAC7512)	+= ti_dac7512.o
obj-$(CONFIG_C2PORT)		+= c2port/

drivers/misc/ep93xx_pwm.c

deleted100644 → 0
+0 −286
Original line number Diff line number Diff line
/*
 *  Simple PWM driver for EP93XX
 *
 *	(c) Copyright 2009  Matthieu Crapet <mcrapet@gmail.com>
 *	(c) Copyright 2009  H Hartley Sweeten <hsweeten@visionengravers.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.
 *
 *  EP9307 has only one channel:
 *    - PWMOUT
 *
 *  EP9301/02/12/15 have two channels:
 *    - PWMOUT
 *    - PWMOUT1 (alternate function for EGPIO14)
 */

#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 <mach/platform.h>

#define EP93XX_PWMx_TERM_COUNT	0x00
#define EP93XX_PWMx_DUTY_CYCLE	0x04
#define EP93XX_PWMx_ENABLE	0x08
#define EP93XX_PWMx_INVERT	0x0C

#define EP93XX_PWM_MAX_COUNT	0xFFFF

struct ep93xx_pwm {
	void __iomem	*mmio_base;
	struct clk	*clk;
	u32		duty_percent;
};

/*
 * /sys/devices/platform/ep93xx-pwm.N
 *   /min_freq      read-only   minimum pwm output frequency
 *   /max_req       read-only   maximum pwm output frequency
 *   /freq          read-write  pwm output frequency (0 = disable output)
 *   /duty_percent  read-write  pwm duty cycle percent (1..99)
 *   /invert        read-write  invert pwm output
 */

static ssize_t ep93xx_pwm_get_min_freq(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
	unsigned long rate = clk_get_rate(pwm->clk);

	return sprintf(buf, "%ld\n", rate / (EP93XX_PWM_MAX_COUNT + 1));
}

static ssize_t ep93xx_pwm_get_max_freq(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
	unsigned long rate = clk_get_rate(pwm->clk);

	return sprintf(buf, "%ld\n", rate / 2);
}

static ssize_t ep93xx_pwm_get_freq(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);

	if (readl(pwm->mmio_base + EP93XX_PWMx_ENABLE) & 0x1) {
		unsigned long rate = clk_get_rate(pwm->clk);
		u16 term = readl(pwm->mmio_base + EP93XX_PWMx_TERM_COUNT);

		return sprintf(buf, "%ld\n", rate / (term + 1));
	} else {
		return sprintf(buf, "disabled\n");
	}
}

static ssize_t ep93xx_pwm_set_freq(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
	long val;
	int err;

	err = kstrtol(buf, 10, &val);
	if (err)
		return -EINVAL;

	if (val == 0) {
		writel(0x0, pwm->mmio_base + EP93XX_PWMx_ENABLE);
	} else if (val <= (clk_get_rate(pwm->clk) / 2)) {
		u32 term, duty;

		val = (clk_get_rate(pwm->clk) / val) - 1;
		if (val > EP93XX_PWM_MAX_COUNT)
			val = EP93XX_PWM_MAX_COUNT;
		if (val < 1)
			val = 1;

		term = readl(pwm->mmio_base + EP93XX_PWMx_TERM_COUNT);
		duty = ((val + 1) * pwm->duty_percent / 100) - 1;

		/* If pwm is running, order is important */
		if (val > term) {
			writel(val, pwm->mmio_base + EP93XX_PWMx_TERM_COUNT);
			writel(duty, pwm->mmio_base + EP93XX_PWMx_DUTY_CYCLE);
		} else {
			writel(duty, pwm->mmio_base + EP93XX_PWMx_DUTY_CYCLE);
			writel(val, pwm->mmio_base + EP93XX_PWMx_TERM_COUNT);
		}

		if (!readl(pwm->mmio_base + EP93XX_PWMx_ENABLE) & 0x1)
			writel(0x1, pwm->mmio_base + EP93XX_PWMx_ENABLE);
	} else {
		return -EINVAL;
	}

	return count;
}

static ssize_t ep93xx_pwm_get_duty_percent(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);

	return sprintf(buf, "%d\n", pwm->duty_percent);
}

static ssize_t ep93xx_pwm_set_duty_percent(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
	long val;
	int err;

	err = kstrtol(buf, 10, &val);
	if (err)
		return -EINVAL;

	if (val > 0 && val < 100) {
		u32 term = readl(pwm->mmio_base + EP93XX_PWMx_TERM_COUNT);
		u32 duty = ((term + 1) * val / 100) - 1;

		writel(duty, pwm->mmio_base + EP93XX_PWMx_DUTY_CYCLE);
		pwm->duty_percent = val;
		return count;
	}

	return -EINVAL;
}

static ssize_t ep93xx_pwm_get_invert(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
	int inverted = readl(pwm->mmio_base + EP93XX_PWMx_INVERT) & 0x1;

	return sprintf(buf, "%d\n", inverted);
}

static ssize_t ep93xx_pwm_set_invert(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);
	long val;
	int err;

	err = kstrtol(buf, 10, &val);
	if (err)
		return -EINVAL;

	if (val == 0)
		writel(0x0, pwm->mmio_base + EP93XX_PWMx_INVERT);
	else if (val == 1)
		writel(0x1, pwm->mmio_base + EP93XX_PWMx_INVERT);
	else
		return -EINVAL;

	return count;
}

static DEVICE_ATTR(min_freq, S_IRUGO, ep93xx_pwm_get_min_freq, NULL);
static DEVICE_ATTR(max_freq, S_IRUGO, ep93xx_pwm_get_max_freq, NULL);
static DEVICE_ATTR(freq, S_IWUSR | S_IRUGO,
		   ep93xx_pwm_get_freq, ep93xx_pwm_set_freq);
static DEVICE_ATTR(duty_percent, S_IWUSR | S_IRUGO,
		   ep93xx_pwm_get_duty_percent, ep93xx_pwm_set_duty_percent);
static DEVICE_ATTR(invert, S_IWUSR | S_IRUGO,
		   ep93xx_pwm_get_invert, ep93xx_pwm_set_invert);

static struct attribute *ep93xx_pwm_attrs[] = {
	&dev_attr_min_freq.attr,
	&dev_attr_max_freq.attr,
	&dev_attr_freq.attr,
	&dev_attr_duty_percent.attr,
	&dev_attr_invert.attr,
	NULL
};

static const struct attribute_group ep93xx_pwm_sysfs_files = {
	.attrs	= ep93xx_pwm_attrs,
};

static int ep93xx_pwm_probe(struct platform_device *pdev)
{
	struct ep93xx_pwm *pwm;
	struct resource *res;
	int ret;

	pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
	if (!pwm)
		return -ENOMEM;

	pwm->clk = devm_clk_get(&pdev->dev, "pwm_clk");
	if (IS_ERR(pwm->clk))
		return PTR_ERR(pwm->clk);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	pwm->mmio_base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(pwm->mmio_base))
		return PTR_ERR(pwm->mmio_base);

	ret = ep93xx_pwm_acquire_gpio(pdev);
	if (ret)
		return ret;

	ret = sysfs_create_group(&pdev->dev.kobj, &ep93xx_pwm_sysfs_files);
	if (ret) {
		ep93xx_pwm_release_gpio(pdev);
		return ret;
	}

	pwm->duty_percent = 50;

	/* disable pwm at startup. Avoids zero value. */
	writel(0x0, pwm->mmio_base + EP93XX_PWMx_ENABLE);
	writel(EP93XX_PWM_MAX_COUNT, pwm->mmio_base + EP93XX_PWMx_TERM_COUNT);
	writel(EP93XX_PWM_MAX_COUNT/2, pwm->mmio_base + EP93XX_PWMx_DUTY_CYCLE);

	clk_enable(pwm->clk);

	platform_set_drvdata(pdev, pwm);
	return 0;
}

static int ep93xx_pwm_remove(struct platform_device *pdev)
{
	struct ep93xx_pwm *pwm = platform_get_drvdata(pdev);

	writel(0x0, pwm->mmio_base + EP93XX_PWMx_ENABLE);
	clk_disable(pwm->clk);
	sysfs_remove_group(&pdev->dev.kobj, &ep93xx_pwm_sysfs_files);
	ep93xx_pwm_release_gpio(pdev);

	return 0;
}

static struct platform_driver ep93xx_pwm_driver = {
	.driver		= {
		.name	= "ep93xx-pwm",
		.owner	= THIS_MODULE,
	},
	.probe		= ep93xx_pwm_probe,
	.remove		= ep93xx_pwm_remove,
};
module_platform_driver(ep93xx_pwm_driver);

MODULE_AUTHOR("Matthieu Crapet <mcrapet@gmail.com>, "
	      "H Hartley Sweeten <hsweeten@visionengravers.com>");
MODULE_DESCRIPTION("EP93xx PWM driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:ep93xx-pwm");
+9 −0
Original line number Diff line number Diff line
@@ -62,6 +62,15 @@ config PWM_BFIN
	  To compile this driver as a module, choose M here: the module
	  will be called pwm-bfin.

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_IMX
	tristate "i.MX PWM support"
	depends on ARCH_MXC
+1 −0
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@ obj-$(CONFIG_PWM_SYSFS) += sysfs.o
obj-$(CONFIG_PWM_AB8500)	+= pwm-ab8500.o
obj-$(CONFIG_PWM_ATMEL_TCB)	+= pwm-atmel-tcb.o
obj-$(CONFIG_PWM_BFIN)		+= pwm-bfin.o
obj-$(CONFIG_PWM_EP93XX)	+= pwm-ep93xx.o
obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o
obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
obj-$(CONFIG_PWM_LPC32XX)	+= pwm-lpc32xx.o
Loading