diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2009-09-14 17:48:14 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-09-14 17:48:14 -0700 |
commit | 2ca7d674d7ab2220707b2ada0b690c0e7c95e7ac (patch) | |
tree | 9c0927ed1d540e5fd704c1f82689870786514655 /drivers | |
parent | 2195d2818c37bdf263865f1e9effccdd9fc5f9d4 (diff) | |
parent | 87d721ad7a37b7650dd710c88dd5c6a5bf9fe996 (diff) |
Merge branch 'devel' of master.kernel.org:/home/rmk/linux-2.6-arm
* 'devel' of master.kernel.org:/home/rmk/linux-2.6-arm: (257 commits)
[ARM] Update mach-types
ARM: 5636/1: Move vendor enum to AMBA include
ARM: Fix pfn_valid() for sparse memory
[ARM] orion5x: Add LaCie NAS 2Big Network support
[ARM] pxa/sharpsl_pm: zaurus c3000 aka spitz: fix resume
ARM: 5686/1: at91: Correct AC97 reset line in at91sam9263ek board
ARM: 5640/1: This patch modifies the support of AC97 on the at91sam9263 ek board
ARM: 5689/1: Update default config of HP Jornada 700-series machines
ARM: 5691/1: fix cache aliasing issues between kmap() and kmap_atomic() with highmem
ARM: 5688/1: ks8695_serial: disable_irq() lockup
ARM: 5687/1: fix an oops with highmem
ARM: 5684/1: Add nuc960 platform to w90x900
ARM: 5683/1: Add nuc950 platform to w90x900
ARM: 5682/1: Add cpu.c and dev.c and modify some files of w90p910 platform
ARM: 5626/1: add suspend/resume functions to amba-pl011 serial driver
ARM: 5625/1: fix hard coded 4K resource size in amba bus detection
MMC: MMCI: convert realview MMC to use gpiolib
ARM: 5685/1: Make MMCI driver compile without gpiolib
ARM: implement highpte
ARM: Show FIQ in /proc/interrupts on CONFIG_FIQ
...
Fix up trivial conflict in arch/arm/kernel/signal.c.
It was due to the TIF_NOTIFY_RESUME addition in commit d0420c83f ("KEYS:
Extend TIF_NOTIFY_RESUME to (almost) all architectures") and follow-ups.
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/amba/bus.c | 26 | ||||
-rw-r--r-- | drivers/hwmon/Kconfig | 17 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 1 | ||||
-rw-r--r-- | drivers/hwmon/s3c-hwmon.c | 405 | ||||
-rw-r--r-- | drivers/input/touchscreen/w90p910_ts.c | 4 | ||||
-rw-r--r-- | drivers/misc/Kconfig | 13 | ||||
-rw-r--r-- | drivers/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/misc/ep93xx_pwm.c | 384 | ||||
-rw-r--r-- | drivers/mmc/host/mmci.c | 79 | ||||
-rw-r--r-- | drivers/mmc/host/mmci.h | 2 | ||||
-rw-r--r-- | drivers/mtd/nand/ts7250.c | 5 | ||||
-rw-r--r-- | drivers/net/Kconfig | 2 | ||||
-rw-r--r-- | drivers/serial/Kconfig | 9 | ||||
-rw-r--r-- | drivers/serial/Makefile | 1 | ||||
-rw-r--r-- | drivers/serial/amba-pl011.c | 26 | ||||
-rw-r--r-- | drivers/serial/imx.c | 65 | ||||
-rw-r--r-- | drivers/serial/serial_ks8695.c | 6 | ||||
-rw-r--r-- | drivers/spi/amba-pl022.c | 2 | ||||
-rw-r--r-- | drivers/video/Kconfig | 4 | ||||
-rw-r--r-- | drivers/video/atmel_lcdfb.c | 6 | ||||
-rw-r--r-- | drivers/video/backlight/Kconfig | 2 | ||||
-rw-r--r-- | drivers/video/imxfb.c | 184 |
22 files changed, 1115 insertions, 129 deletions
diff --git a/drivers/amba/bus.c b/drivers/amba/bus.c index 24665067301..f60b2b6a093 100644 --- a/drivers/amba/bus.c +++ b/drivers/amba/bus.c @@ -204,6 +204,7 @@ static void amba_device_release(struct device *dev) int amba_device_register(struct amba_device *dev, struct resource *parent) { u32 pid, cid; + u32 size; void __iomem *tmp; int i, ret; @@ -229,16 +230,25 @@ int amba_device_register(struct amba_device *dev, struct resource *parent) if (ret) goto err_out; - tmp = ioremap(dev->res.start, SZ_4K); + /* + * Dynamically calculate the size of the resource + * and use this for iomap + */ + size = resource_size(&dev->res); + tmp = ioremap(dev->res.start, size); if (!tmp) { ret = -ENOMEM; goto err_release; } + /* + * Read pid and cid based on size of resource + * they are located at end of region + */ for (pid = 0, i = 0; i < 4; i++) - pid |= (readl(tmp + 0xfe0 + 4 * i) & 255) << (i * 8); + pid |= (readl(tmp + size - 0x20 + 4 * i) & 255) << (i * 8); for (cid = 0, i = 0; i < 4; i++) - cid |= (readl(tmp + 0xff0 + 4 * i) & 255) << (i * 8); + cid |= (readl(tmp + size - 0x10 + 4 * i) & 255) << (i * 8); iounmap(tmp); @@ -353,11 +363,14 @@ amba_find_device(const char *busid, struct device *parent, unsigned int id, int amba_request_regions(struct amba_device *dev, const char *name) { int ret = 0; + u32 size; if (!name) name = dev->dev.driver->name; - if (!request_mem_region(dev->res.start, SZ_4K, name)) + size = resource_size(&dev->res); + + if (!request_mem_region(dev->res.start, size, name)) ret = -EBUSY; return ret; @@ -371,7 +384,10 @@ int amba_request_regions(struct amba_device *dev, const char *name) */ void amba_release_regions(struct amba_device *dev) { - release_mem_region(dev->res.start, SZ_4K); + u32 size; + + size = resource_size(&dev->res); + release_mem_region(dev->res.start, size); } EXPORT_SYMBOL(amba_driver_register); diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 2d5016691d4..2e25b7a827d 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -702,6 +702,23 @@ config SENSORS_SHT15 This driver can also be built as a module. If so, the module will be called sht15. +config SENSORS_S3C + tristate "S3C24XX/S3C64XX Inbuilt ADC" + depends on ARCH_S3C2410 || ARCH_S3C64XX + help + If you say yes here you get support for the on-board ADCs of + the Samsung S3C24XX or S3C64XX series of SoC + + This driver can also be built as a module. If so, the module + will be called s3c-hwmo. + +config SENSORS_S3C_RAW + bool "Include raw channel attributes in sysfs" + depends on SENSORS_S3C + help + Say Y here if you want to include raw copies of all the ADC + channels in sysfs. + config SENSORS_SIS5595 tristate "Silicon Integrated Systems Corp. SiS5595" depends on PCI diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index b793dce6bed..7f239a247c3 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_SENSORS_MAX6650) += max6650.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o obj-$(CONFIG_SENSORS_PC87427) += pc87427.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o +obj-$(CONFIG_SENSORS_S3C) += s3c-hwmon.o obj-$(CONFIG_SENSORS_SHT15) += sht15.o obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o diff --git a/drivers/hwmon/s3c-hwmon.c b/drivers/hwmon/s3c-hwmon.c new file mode 100644 index 00000000000..3a524f2fe49 --- /dev/null +++ b/drivers/hwmon/s3c-hwmon.c @@ -0,0 +1,405 @@ +/* linux/drivers/hwmon/s3c-hwmon.c + * + * Copyright (C) 2005, 2008, 2009 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks <ben@simtec.co.uk> + * + * S3C24XX/S3C64XX ADC hwmon support + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> + +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> + +#include <plat/adc.h> +#include <plat/hwmon.h> + +struct s3c_hwmon_attr { + struct sensor_device_attribute in; + struct sensor_device_attribute label; + char in_name[12]; + char label_name[12]; +}; + +/** + * struct s3c_hwmon - ADC hwmon client information + * @lock: Access lock to serialise the conversions. + * @client: The client we registered with the S3C ADC core. + * @hwmon_dev: The hwmon device we created. + * @attr: The holders for the channel attributes. +*/ +struct s3c_hwmon { + struct semaphore lock; + struct s3c_adc_client *client; + struct device *hwmon_dev; + + struct s3c_hwmon_attr attrs[8]; +}; + +/** + * s3c_hwmon_read_ch - read a value from a given adc channel. + * @dev: The device. + * @hwmon: Our state. + * @channel: The channel we're reading from. + * + * Read a value from the @channel with the proper locking and sleep until + * either the read completes or we timeout awaiting the ADC core to get + * back to us. + */ +static int s3c_hwmon_read_ch(struct device *dev, + struct s3c_hwmon *hwmon, int channel) +{ + int ret; + + ret = down_interruptible(&hwmon->lock); + if (ret < 0) + return ret; + + dev_dbg(dev, "reading channel %d\n", channel); + + ret = s3c_adc_read(hwmon->client, channel); + up(&hwmon->lock); + + return ret; +} + +#ifdef CONFIG_SENSORS_S3C_RAW +/** + * s3c_hwmon_show_raw - show a conversion from the raw channel number. + * @dev: The device that the attribute belongs to. + * @attr: The attribute being read. + * @buf: The result buffer. + * + * This show deals with the raw attribute, registered for each possible + * ADC channel. This does a conversion and returns the raw (un-scaled) + * value returned from the hardware. + */ +static ssize_t s3c_hwmon_show_raw(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct s3c_hwmon *adc = platform_get_drvdata(to_platform_device(dev)); + struct sensor_device_attribute *sa = to_sensor_dev_attr(attr); + int ret; + + ret = s3c_hwmon_read_ch(dev, adc, sa->index); + + return (ret < 0) ? ret : snprintf(buf, PAGE_SIZE, "%d\n", ret); +} + +#define DEF_ADC_ATTR(x) \ + static SENSOR_DEVICE_ATTR(adc##x##_raw, S_IRUGO, s3c_hwmon_show_raw, NULL, x) + +DEF_ADC_ATTR(0); +DEF_ADC_ATTR(1); +DEF_ADC_ATTR(2); +DEF_ADC_ATTR(3); +DEF_ADC_ATTR(4); +DEF_ADC_ATTR(5); +DEF_ADC_ATTR(6); +DEF_ADC_ATTR(7); + +static struct attribute *s3c_hwmon_attrs[9] = { + &sensor_dev_attr_adc0_raw.dev_attr.attr, + &sensor_dev_attr_adc1_raw.dev_attr.attr, + &sensor_dev_attr_adc2_raw.dev_attr.attr, + &sensor_dev_attr_adc3_raw.dev_attr.attr, + &sensor_dev_attr_adc4_raw.dev_attr.attr, + &sensor_dev_attr_adc5_raw.dev_attr.attr, + &sensor_dev_attr_adc6_raw.dev_attr.attr, + &sensor_dev_attr_adc7_raw.dev_attr.attr, + NULL, +}; + +static struct attribute_group s3c_hwmon_attrgroup = { + .attrs = s3c_hwmon_attrs, +}; + +static inline int s3c_hwmon_add_raw(struct device *dev) +{ + return sysfs_create_group(&dev->kobj, &s3c_hwmon_attrgroup); +} + +static inline void s3c_hwmon_remove_raw(struct device *dev) +{ + sysfs_remove_group(&dev->kobj, &s3c_hwmon_attrgroup); +} + +#else + +static inline int s3c_hwmon_add_raw(struct device *dev) { return 0; } +static inline void s3c_hwmon_remove_raw(struct device *dev) { } + +#endif /* CONFIG_SENSORS_S3C_RAW */ + +/** + * s3c_hwmon_ch_show - show value of a given channel + * @dev: The device that the attribute belongs to. + * @attr: The attribute being read. + * @buf: The result buffer. + * + * Read a value from the ADC and scale it before returning it to the + * caller. The scale factor is gained from the channel configuration + * passed via the platform data when the device was registered. + */ +static ssize_t s3c_hwmon_ch_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sen_attr = to_sensor_dev_attr(attr); + struct s3c_hwmon *hwmon = platform_get_drvdata(to_platform_device(dev)); + struct s3c_hwmon_pdata *pdata = dev->platform_data; + struct s3c_hwmon_chcfg *cfg; + int ret; + + cfg = pdata->in[sen_attr->index]; + + ret = s3c_hwmon_read_ch(dev, hwmon, sen_attr->index); + if (ret < 0) + return ret; + + ret *= cfg->mult; + ret = DIV_ROUND_CLOSEST(ret, cfg->div); + + return snprintf(buf, PAGE_SIZE, "%d\n", ret); +} + +/** + * s3c_hwmon_label_show - show label name of the given channel. + * @dev: The device that the attribute belongs to. + * @attr: The attribute being read. + * @buf: The result buffer. + * + * Return the label name of a given channel + */ +static ssize_t s3c_hwmon_label_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sen_attr = to_sensor_dev_attr(attr); + struct s3c_hwmon_pdata *pdata = dev->platform_data; + struct s3c_hwmon_chcfg *cfg; + + cfg = pdata->in[sen_attr->index]; + + return snprintf(buf, PAGE_SIZE, "%s\n", cfg->name); +} + +/** + * s3c_hwmon_create_attr - create hwmon attribute for given channel. + * @dev: The device to create the attribute on. + * @cfg: The channel configuration passed from the platform data. + * @channel: The ADC channel number to process. + * + * Create the scaled attribute for use with hwmon from the specified + * platform data in @pdata. The sysfs entry is handled by the routine + * s3c_hwmon_ch_show(). + * + * The attribute name is taken from the configuration data if present + * otherwise the name is taken by concatenating in_ with the channel + * number. + */ +static int s3c_hwmon_create_attr(struct device *dev, + struct s3c_hwmon_chcfg *cfg, + struct s3c_hwmon_attr *attrs, + int channel) +{ + struct sensor_device_attribute *attr; + int ret; + + snprintf(attrs->in_name, sizeof(attrs->in_name), "in%d_input", channel); + + attr = &attrs->in; + attr->index = channel; + attr->dev_attr.attr.name = attrs->in_name; + attr->dev_attr.attr.mode = S_IRUGO; + attr->dev_attr.attr.owner = THIS_MODULE; + attr->dev_attr.show = s3c_hwmon_ch_show; + + ret = device_create_file(dev, &attr->dev_attr); + if (ret < 0) { + dev_err(dev, "failed to create input attribute\n"); + return ret; + } + + /* if this has a name, add a label */ + if (cfg->name) { + snprintf(attrs->label_name, sizeof(attrs->label_name), + "in%d_label", channel); + + attr = &attrs->label; + attr->index = channel; + attr->dev_attr.attr.name = attrs->label_name; + attr->dev_attr.attr.mode = S_IRUGO; + attr->dev_attr.attr.owner = THIS_MODULE; + attr->dev_attr.show = s3c_hwmon_label_show; + + ret = device_create_file(dev, &attr->dev_attr); + if (ret < 0) { + device_remove_file(dev, &attrs->in.dev_attr); + dev_err(dev, "failed to create label attribute\n"); + } + } + + return ret; +} + +static void s3c_hwmon_remove_attr(struct device *dev, + struct s3c_hwmon_attr *attrs) +{ + device_remove_file(dev, &attrs->in.dev_attr); + device_remove_file(dev, &attrs->label.dev_attr); +} + +/** + * s3c_hwmon_probe - device probe entry. + * @dev: The device being probed. +*/ +static int __devinit s3c_hwmon_probe(struct platform_device *dev) +{ + struct s3c_hwmon_pdata *pdata = dev->dev.platform_data; + struct s3c_hwmon *hwmon; + int ret = 0; + int i; + + if (!pdata) { + dev_err(&dev->dev, "no platform data supplied\n"); + return -EINVAL; + } + + hwmon = kzalloc(sizeof(struct s3c_hwmon), GFP_KERNEL); + if (hwmon == NULL) { + dev_err(&dev->dev, "no memory\n"); + return -ENOMEM; + } + + platform_set_drvdata(dev, hwmon); + + init_MUTEX(&hwmon->lock); + + /* Register with the core ADC driver. */ + + hwmon->client = s3c_adc_register(dev, NULL, NULL, 0); + if (IS_ERR(hwmon->client)) { + dev_err(&dev->dev, "cannot register adc\n"); + ret = PTR_ERR(hwmon->client); + goto err_mem; + } + + /* add attributes for our adc devices. */ + + ret = s3c_hwmon_add_raw(&dev->dev); + if (ret) + goto err_registered; + + /* register with the hwmon core */ + + hwmon->hwmon_dev = hwmon_device_register(&dev->dev); + if (IS_ERR(hwmon->hwmon_dev)) { + dev_err(&dev->dev, "error registering with hwmon\n"); + ret = PTR_ERR(hwmon->hwmon_dev); + goto err_raw_attribute; + } + + for (i = 0; i < ARRAY_SIZE(pdata->in); i++) { + if (!pdata->in[i]) + continue; + + if (pdata->in[i]->mult >= 0x10000) + dev_warn(&dev->dev, + "channel %d multiplier too large\n", + i); + + ret = s3c_hwmon_create_attr(&dev->dev, pdata->in[i], + &hwmon->attrs[i], i); + if (ret) { + dev_err(&dev->dev, + "error creating channel %d\n", i); + + for (i--; i >= 0; i--) + s3c_hwmon_remove_attr(&dev->dev, + &hwmon->attrs[i]); + + goto err_hwmon_register; + } + } + + return 0; + + err_hwmon_register: + hwmon_device_unregister(hwmon->hwmon_dev); + + err_raw_attribute: + s3c_hwmon_remove_raw(&dev->dev); + + err_registered: + s3c_adc_release(hwmon->client); + + err_mem: + kfree(hwmon); + return ret; +} + +static int __devexit s3c_hwmon_remove(struct platform_device *dev) +{ + struct s3c_hwmon *hwmon = platform_get_drvdata(dev); + int i; + + s3c_hwmon_remove_raw(&dev->dev); + + for (i = 0; i < ARRAY_SIZE(hwmon->attrs); i++) + s3c_hwmon_remove_attr(&dev->dev, &hwmon->attrs[i]); + + hwmon_device_unregister(hwmon->hwmon_dev); + s3c_adc_release(hwmon->client); + + return 0; +} + +static struct platform_driver s3c_hwmon_driver = { + .driver = { + .name = "s3c-hwmon", + .owner = THIS_MODULE, + }, + .probe = s3c_hwmon_probe, + .remove = __devexit_p(s3c_hwmon_remove), +}; + +static int __init s3c_hwmon_init(void) +{ + return platform_driver_register(&s3c_hwmon_driver); +} + +static void __exit s3c_hwmon_exit(void) +{ + platform_driver_unregister(&s3c_hwmon_driver); +} + +module_init(s3c_hwmon_init); +module_exit(s3c_hwmon_exit); + +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); +MODULE_DESCRIPTION("S3C ADC HWMon driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:s3c-hwmon"); diff --git a/drivers/input/touchscreen/w90p910_ts.c b/drivers/input/touchscreen/w90p910_ts.c index 6071f588257..937dfe4e9b1 100644 --- a/drivers/input/touchscreen/w90p910_ts.c +++ b/drivers/input/touchscreen/w90p910_ts.c @@ -326,7 +326,7 @@ static struct platform_driver w90x900ts_driver = { .probe = w90x900ts_probe, .remove = __devexit_p(w90x900ts_remove), .driver = { - .name = "w90x900-ts", + .name = "nuc900-ts", .owner = THIS_MODULE, }, }; @@ -347,4 +347,4 @@ module_exit(w90x900ts_exit); MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>"); MODULE_DESCRIPTION("w90p910 touch screen driver!"); MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:w90p910-ts"); +MODULE_ALIAS("platform:nuc900-ts"); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 68ab39d7cb3..df1f86b5c83 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -233,6 +233,19 @@ config ISL29003 This driver can also be built as a module. If so, the module will be called isl29003. +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. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 36f733cd60e..f982d2ecfde 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_SGI_XP) += sgi-xp/ obj-$(CONFIG_SGI_GRU) += sgi-gru/ obj-$(CONFIG_HP_ILO) += hpilo.o obj-$(CONFIG_ISL29003) += isl29003.o +obj-$(CONFIG_EP93XX_PWM) += ep93xx_pwm.o obj-$(CONFIG_C2PORT) += c2port/ obj-y += eeprom/ obj-y += cb710/ diff --git a/drivers/misc/ep93xx_pwm.c b/drivers/misc/ep93xx_pwm.c new file mode 100644 index 00000000000..ba4694169d7 --- /dev/null +++ b/drivers/misc/ep93xx_pwm.c @@ -0,0 +1,384 @@ +/* + * 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/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; +}; + +static inline void ep93xx_pwm_writel(struct ep93xx_pwm *pwm, + unsigned int val, unsigned int off) +{ + __raw_writel(val, pwm->mmio_base + off); +} + +static inline unsigned int ep93xx_pwm_readl(struct ep93xx_pwm *pwm, + unsigned int off) +{ + return __raw_readl(pwm->mmio_base + off); +} + +static inline void ep93xx_pwm_write_tc(struct ep93xx_pwm *pwm, u16 value) +{ + ep93xx_pwm_writel(pwm, value, EP93XX_PWMx_TERM_COUNT); +} + +static inline u16 ep93xx_pwm_read_tc(struct ep93xx_pwm *pwm) +{ + return ep93xx_pwm_readl(pwm, EP93XX_PWMx_TERM_COUNT); +} + +static inline void ep93xx_pwm_write_dc(struct ep93xx_pwm *pwm, u16 value) +{ + ep93xx_pwm_writel(pwm, value, EP93XX_PWMx_DUTY_CYCLE); +} + +static inline void ep93xx_pwm_enable(struct ep93xx_pwm *pwm) +{ + ep93xx_pwm_writel(pwm, 0x1, EP93XX_PWMx_ENABLE); +} + +static inline void ep93xx_pwm_disable(struct ep93xx_pwm *pwm) +{ + ep93xx_pwm_writel(pwm, 0x0, EP93XX_PWMx_ENABLE); +} + +static inline int ep93xx_pwm_is_enabled(struct ep93xx_pwm *pwm) +{ + return ep93xx_pwm_readl(pwm, EP93XX_PWMx_ENABLE) & 0x1; +} + +static inline void ep93xx_pwm_invert(struct ep93xx_pwm *pwm) +{ + ep93xx_pwm_writel(pwm, 0x1, EP93XX_PWMx_INVERT); +} + +static inline void ep93xx_pwm_normal(struct ep93xx_pwm *pwm) +{ + ep93xx_pwm_writel(pwm, 0x0, EP93XX_PWMx_INVERT); +} + +static inline int ep93xx_pwm_is_inverted(struct ep93xx_pwm *pwm) +{ + return ep93xx_pwm_readl(pwm, EP93XX_PWMx_INVERT) & 0x1; +} + +/* + * /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 (ep93xx_pwm_is_enabled(pwm)) { + unsigned long rate = clk_get_rate(pwm->clk); + u16 term = ep93xx_pwm_read_tc(pwm); + + 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 = strict_strtol(buf, 10, &val); + if (err) + return -EINVAL; + + if (val == 0) { + ep93xx_pwm_disable(pwm); + } 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 = ep93xx_pwm_read_tc(pwm); + duty = ((val + 1) * pwm->duty_percent / 100) - 1; + + /* If pwm is running, order is important */ + if (val > term) { + ep93xx_pwm_write_tc(pwm, val); + ep93xx_pwm_write_dc(pwm, duty); + } else { + ep93xx_pwm_write_dc(pwm, duty); + ep93xx_pwm_write_tc(pwm, val); + } + + if (!ep93xx_pwm_is_enabled(pwm)) + ep93xx_pwm_enable(pwm); + } 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 = strict_strtol(buf, 10, &val); + if (err) + return -EINVAL; + + if (val > 0 && val < 100) { + u32 term = ep93xx_pwm_read_tc(pwm); + ep93xx_pwm_write_dc(pwm, ((term + 1) * val / 100) - 1); + 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); + + return sprintf(buf, "%d\n", ep93xx_pwm_is_inverted(pwm)); +} + +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 = strict_strtol(buf, 10, &val); + if (err) + return -EINVAL; + + if (val == 0) + ep93xx_pwm_normal(pwm); + else if (val == 1) + ep93xx_pwm_invert(pwm); + 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_IWUGO | S_IRUGO, + ep93xx_pwm_get_freq, ep93xx_pwm_set_freq); +static DEVICE_ATTR(duty_percent, S_IWUGO | S_IRUGO, + ep93xx_pwm_get_duty_percent, ep93xx_pwm_set_duty_percent); +static DEVICE_ATTR(invert, S_IWUGO | 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 __init ep93xx_pwm_probe(struct platform_device *pdev) +{ + struct ep93xx_pwm *pwm; + struct resource *res; + int err; + + err = ep93xx_pwm_acquire_gpio(pdev); + if (err) + return err; + + pwm = kzalloc(sizeof(struct ep93xx_pwm), GFP_KERNEL); + if (!pwm) { + err = -ENOMEM; + goto fail_no_mem; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + err = -ENXIO; + goto fail_no_mem_resource; + } + + res = request_mem_region(res->start, resource_size(res), pdev->name); + if (res == NULL) { + err = -EBUSY; + goto fail_no_mem_resource; + } + + pwm->mmio_base = ioremap(res->start, resource_size(res)); + if (pwm->mmio_base == NULL) { + err = -ENXIO; + goto fail_no_ioremap; + } + + err = sysfs_create_group(&pdev->dev.kobj, &ep93xx_pwm_sysfs_files); + if (err) + goto fail_no_sysfs; + + pwm->clk = clk_get(&pdev->dev, "pwm_clk"); + if (IS_ERR(pwm->clk)) { + err = PTR_ERR(pwm->clk); + goto fail_no_clk; + } + + pwm->duty_percent = 50; + + platform_set_drvdata(pdev, pwm); + + /* disable pwm at startup. Avoids zero value. */ + ep93xx_pwm_disable(pwm); + ep93xx_pwm_write_tc(pwm, EP93XX_PWM_MAX_COUNT); + ep93xx_pwm_write_dc(pwm, EP93XX_PWM_MAX_COUNT / 2); + + clk_enable(pwm->clk); + + return 0; + +fail_no_clk: + sysfs_remove_group(&pdev->dev.kobj, &ep93xx_pwm_sysfs_files); +fail_no_sysfs: + iounmap(pwm->mmio_base); +fail_no_ioremap: + release_mem_region(res->start, resource_size(res)); +fail_no_mem_resource: + kfree(pwm); +fail_no_mem: + ep93xx_pwm_release_gpio(pdev); + return err; +} + +static int __exit ep93xx_pwm_remove(struct platform_device *pdev) +{ + struct ep93xx_pwm *pwm = platform_get_drvdata(pdev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + ep93xx_pwm_disable(pwm); + clk_disable(pwm->clk); + clk_put(pwm->clk); + platform_set_drvdata(pdev, NULL); + sysfs_remove_group(&pdev->dev.kobj, &ep93xx_pwm_sysfs_files); + iounmap(pwm->mmio_base); + release_mem_region(res->start, resource_size(res)); + kfree(pwm);< |