aboutsummaryrefslogtreecommitdiff
path: root/arch/arm/mach-ux500/pwm.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-ux500/pwm.c')
-rw-r--r--arch/arm/mach-ux500/pwm.c460
1 files changed, 460 insertions, 0 deletions
diff --git a/arch/arm/mach-ux500/pwm.c b/arch/arm/mach-ux500/pwm.c
new file mode 100644
index 00000000000..b0dd2da3397
--- /dev/null
+++ b/arch/arm/mach-ux500/pwm.c
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License v2
+ * Author: Stefan Nilsson <stefan.xk.nilsson@stericsson.com> for
+ * ST-Ericsson.
+ * Author: Magnus Templing <magnus.templing@stericsson.com> for
+ * ST-Ericsson.
+ *
+ * PWM (Pulse Width Modulator) driver for the DB5500 digital baseband
+ * controller. Based on arch/arm/mach-pxa/pwm.c.
+ */
+#include <linux/err.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+/* Register offsets */
+#define PWM_CONTROL_REG_OFFSET 0x00
+#define PWM_DUTY_REG_OFFSET 0x04
+#define PWM_PERIOD_REG_OFFSET 0x08
+#define PWM_BURST_REG_OFFSET 0x10
+#define PWM_SEQUENCE_REG_OFFSET 0x14
+#define PWM_DELAY_REG_OFFSET 0x18
+
+/* CONTROL_REG */
+#define PWM_CONTROL_REG_ENABLE_POS 0
+#define PWM_CONTROL_REG_CBM_POS 1
+#define PWM_CONTROL_REG_PRESCALER_POS 2
+#define PWM_CONTROL_REG_DISABLE 0
+#define PWM_CONTROL_REG_ENABLE 1
+#define PWM_CONTROL_REG_CBM_ENABLE 1
+
+#define PWM_BURST_REG_ONE_PULSE_PER_BURST 0
+#define PWM_SEQUENCE_REG_ONE_BURST_PER_SEQUENCE 0
+#define PWM_DELAY_REG_NO_DELAY 0
+#define PWM_PRESCALE 25
+
+struct pwm_device {
+ struct list_head node;
+ struct platform_device *pdev;
+
+ void __iomem *mmio_base;
+
+ unsigned int pwm_id;
+ unsigned int use_count;
+};
+
+static DEFINE_MUTEX(pwm_lock);
+static LIST_HEAD(pwm_list);
+#if defined(CONFIG_DEBUG_FS)
+static void init_debugfs(void);
+#else
+#define init_debugfs(x)
+#endif
+
+/*
+ * PWM_CLK_RATE = 26000000 Hz
+ *
+ * period_ns = 10^9 * (PWM_PRESCALE + 1) * period / PWM_CLK_RATE
+ * duty_ns = 10^9 * (PWM_PRESCALE + 1) * dc / PWM_CLK_RATE
+ *
+ * period = period_ns * 26000000 / 10^9 / 26 => period = period_ns / 1000
+ * dc = duty_ns * 26000000 / 10^9 / 26 => dc = duty_ns / 1000
+ */
+#define MAGIC_DIVISOR (1000)
+int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
+{
+ u32 period;
+ u32 dc;
+
+ dev_dbg(&pwm->pdev->dev, "%s duty_ns: %d period_ns: %d\n",
+ __func__, duty_ns, period_ns);
+
+ if (pwm == NULL || period_ns == 0 || duty_ns >= period_ns) {
+ dev_err(&pwm->pdev->dev, "%s INVALID ARGUMENTS!\n", __func__);
+ return -EINVAL;
+ }
+
+ period = period_ns / MAGIC_DIVISOR;
+ dc = duty_ns / MAGIC_DIVISOR;
+
+ writel(period, pwm->mmio_base + PWM_PERIOD_REG_OFFSET);
+ writel(dc, pwm->mmio_base + PWM_DUTY_REG_OFFSET);
+ writel(PWM_BURST_REG_ONE_PULSE_PER_BURST,
+ pwm->mmio_base + PWM_BURST_REG_OFFSET);
+ writel(PWM_SEQUENCE_REG_ONE_BURST_PER_SEQUENCE,
+ pwm->mmio_base + PWM_SEQUENCE_REG_OFFSET);
+ writel(PWM_DELAY_REG_NO_DELAY, pwm->mmio_base + PWM_DELAY_REG_OFFSET);
+
+ return 0;
+}
+EXPORT_SYMBOL(pwm_config);
+
+int pwm_enable(struct pwm_device *pwm)
+{
+ u32 val;
+
+ val = (PWM_CONTROL_REG_ENABLE << PWM_CONTROL_REG_ENABLE_POS);
+ val |= (PWM_CONTROL_REG_CBM_ENABLE << PWM_CONTROL_REG_CBM_POS);
+ val |= (PWM_PRESCALE << PWM_CONTROL_REG_PRESCALER_POS);
+
+ writel(val, pwm->mmio_base + PWM_CONTROL_REG_OFFSET);
+
+ return 0;
+}
+EXPORT_SYMBOL(pwm_enable);
+
+void pwm_disable(struct pwm_device *pwm)
+{
+ writel(PWM_CONTROL_REG_DISABLE,
+ pwm->mmio_base + PWM_CONTROL_REG_OFFSET);
+}
+EXPORT_SYMBOL(pwm_disable);
+
+struct pwm_device *pwm_request(int pwm_id, const char *label)
+{
+ struct pwm_device *pwm;
+ bool found = false;
+
+ mutex_lock(&pwm_lock);
+
+ list_for_each_entry(pwm, &pwm_list, node) {
+ if (pwm->pwm_id == pwm_id) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ if (pwm->use_count == 0)
+ pwm->use_count++;
+ else
+ pwm = ERR_PTR(-EBUSY);
+ } else {
+ pwm = ERR_PTR(-ENOENT);
+ }
+
+ mutex_unlock(&pwm_lock);
+ return pwm;
+}
+EXPORT_SYMBOL(pwm_request);
+
+void pwm_free(struct pwm_device *pwm)
+{
+ mutex_lock(&pwm_lock);
+
+ if (pwm->use_count)
+ pwm->use_count--;
+ else
+ dev_warn(&pwm->pdev->dev, "PWM device already freed\n");
+
+ mutex_unlock(&pwm_lock);
+}
+EXPORT_SYMBOL(pwm_free);
+
+int __init pwm_probe(struct platform_device *pdev)
+{
+ struct pwm_device *pwm;
+ struct resource *r;
+ int ret = 0;
+
+ dev_dbg(&pdev->dev, "%s\n", __func__);
+
+ pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
+ if (pwm == NULL) {
+ dev_err(&pdev->dev, "failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ pwm->pwm_id = pdev->id;
+ pwm->pdev = pdev;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (r == NULL) {
+ dev_err(&pdev->dev, "no memory resource defined\n");
+ ret = -ENODEV;
+ goto err_free;
+ }
+
+ pwm->mmio_base = ioremap(r->start, resource_size(r));
+ if (pwm->mmio_base == NULL) {
+ dev_err(&pdev->dev, "failed to ioremap() registers\n");
+ ret = -ENODEV;
+ goto err_free;
+ }
+
+ mutex_lock(&pwm_lock);
+ list_add_tail(&pwm->node, &pwm_list);
+ mutex_unlock(&pwm_lock);
+
+ platform_set_drvdata(pdev, pwm);
+
+ init_debugfs();
+
+ return 0;
+
+err_free:
+ kfree(pwm);
+ return ret;
+}
+
+static int __devexit pwm_remove(struct platform_device *pdev)
+{
+ struct pwm_device *pwm;
+
+ pwm = platform_get_drvdata(pdev);
+ if (pwm == NULL)
+ return -ENODEV;
+
+ mutex_lock(&pwm_lock);
+ list_del(&pwm->node);
+ mutex_unlock(&pwm_lock);
+
+ iounmap(pwm->mmio_base);
+
+ kfree(pwm);
+ return 0;
+}
+
+static struct platform_driver pwm_driver = {
+ .driver = {
+ .name = "pwm",
+ .owner = THIS_MODULE,
+ },
+ .remove = __devexit_p(pwm_remove),
+};
+
+static int __init pwm_init(void)
+{
+ int ret = 0;
+
+ ret = platform_driver_probe(&pwm_driver, pwm_probe);
+ if (ret) {
+ pr_err("PWM: probe failed ret=%d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+#if defined(CONFIG_DEBUG_FS)
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+static int pwm_dump(struct seq_file *s, void *data)
+{
+ struct list_head *pos;
+ struct pwm_device *pwm;
+ int pwm_index = 0;
+
+ mutex_lock(&pwm_lock);
+
+ list_for_each(pos, &pwm_list) {
+ pwm = (struct pwm_device *)
+ list_entry(pos, struct pwm_device, node);
+
+ seq_printf(s, "===========================\n"
+ " PWM DUMP %d\n"
+ " mmio_base: 0x%p\n"
+ " CONTROL : 0x%X\n"
+ " DUTY : 0x%X\n"
+ " PERIOD : 0x%X\n"
+ " BURST : 0x%X\n"
+ " SEQUENCE : 0x%X\n"
+ " DELAY : 0x%X\n",
+ pwm_index,
+ pwm->mmio_base,
+ readl(pwm->mmio_base + PWM_CONTROL_REG_OFFSET),
+ readl(pwm->mmio_base + PWM_DUTY_REG_OFFSET),
+ readl(pwm->mmio_base + PWM_PERIOD_REG_OFFSET),
+ readl(pwm->mmio_base + PWM_BURST_REG_OFFSET),
+ readl(pwm->mmio_base + PWM_SEQUENCE_REG_OFFSET),
+ readl(pwm->mmio_base + PWM_DELAY_REG_OFFSET));
+ pwm_index++;
+ }
+
+ mutex_unlock(&pwm_lock);
+
+ return 0;
+}
+
+static int pwm_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, pwm_dump, NULL);
+}
+
+static int get_value(char **c, char *s, int *v)
+{
+ char *val;
+ long value;
+ int ret = -1;
+
+ if (c != NULL) {
+ val = strsep(c, s);
+
+ if (val) {
+ ret = strict_strtol(val, 10, &value);
+
+ if (!ret)
+ *v = value;
+ }
+ }
+
+ return ret;
+}
+
+#define MAX_BUFFER_SIZE 64
+
+static int pwm_write_raw(struct file *file, const char __user *buffer,
+ size_t size, loff_t *offset)
+{
+ char int_buf[MAX_BUFFER_SIZE];
+ char *token;
+ u32 period;
+ u32 dc;
+ u32 burst;
+ u32 seq;
+ u32 delay;
+ u32 ctrl;
+ int max_size = max(size, strlen(buffer));
+
+ struct pwm_device *pwm = (struct pwm_device *)
+ list_first_entry(&pwm_list, struct pwm_device, node);
+
+ /* Copy the buffer since strsep alters it */
+ strncpy((char *) &int_buf, (char*)buffer, max_size);
+
+ token = (char *) &int_buf;
+
+ if (get_value(&token, " ", &period)) {
+ dev_err(&pwm->pdev->dev, "%s 1\n", __func__);
+ return size;
+ }
+
+ if (get_value(&token, " ", &dc)) {
+ dev_err(&pwm->pdev->dev, "%s 2\n", __func__);
+ return size;
+ }
+
+ if (get_value(&token, " ", &burst)) {
+ dev_err(&pwm->pdev->dev, "%s 3\n", __func__);
+ return size;
+ }
+
+ if (get_value(&token, " ", &seq)) {
+ dev_err(&pwm->pdev->dev, "%s 4\n", __func__);
+ return size;
+ }
+
+ if (get_value(&token, " ", &delay)) {
+ dev_err(&pwm->pdev->dev, "%s 5\n", __func__);
+ return size;
+ }
+
+ if (get_value(&token, "\n", &ctrl)) {
+ dev_err(&pwm->pdev->dev, "%s 6\n", __func__);
+ return size;
+ }
+
+ dev_dbg(&pwm->pdev->dev,
+ "%s p: 0x%X d: 0x%X b: 0x%X s: 0x%X d: 0x%X c: 0x%X\n"
+ , __func__, period, dc, burst, seq, delay, ctrl);
+
+ writel(period, pwm->mmio_base + PWM_PERIOD_REG_OFFSET);
+ writel(dc , pwm->mmio_base + PWM_DUTY_REG_OFFSET);
+ writel(burst , pwm->mmio_base + PWM_BURST_REG_OFFSET);
+ writel(seq , pwm->mmio_base + PWM_SEQUENCE_REG_OFFSET);
+ writel(delay , pwm->mmio_base + PWM_DELAY_REG_OFFSET);
+ writel(ctrl , pwm->mmio_base + PWM_CONTROL_REG_OFFSET);
+
+ return size;
+}
+
+static int pwm_write(struct file *file,
+ const char __user *buffer,
+ size_t size, loff_t *offset)
+{
+ char int_buf[MAX_BUFFER_SIZE];
+ char *token;
+ u32 period = 0;
+ u32 dc = 0;
+ u32 ctrl = 0;
+ int max_size = max(size, strlen(buffer));
+
+ struct pwm_device *pwm = (struct pwm_device *)
+ list_first_entry(&pwm_list, struct pwm_device, node);
+
+ /* Copy the buffer since strsep alters it */
+ strncpy((char *) &int_buf, (char*)buffer, max((int) size, max_size));
+
+ token = (char *) &int_buf;
+
+ if (get_value(&token, " ", &period)) {
+ dev_err(&pwm->pdev->dev, "%s 1\n", __func__);
+ return size;
+ }
+
+ if (get_value(&token, " ", &dc)) {
+ dev_err(&pwm->pdev->dev, "%s 2\n", __func__);
+ return size;
+ }
+
+ if (get_value(&token, "\n", &ctrl)) {
+ dev_err(&pwm->pdev->dev, "%s 3\n", __func__);
+ return size;
+ }
+
+ dev_dbg(&pwm->pdev->dev, "%s p: 0x%X d: 0x%X c: 0x%X\n",
+ __func__, period, dc, ctrl);
+
+ if (ctrl == 0)
+ pwm_disable(pwm);
+ else {
+ pwm_config(pwm, dc, period);
+ pwm_enable(pwm);
+ }
+
+ return size;
+}
+
+static const struct file_operations pwm_operations = {
+ .owner = THIS_MODULE,
+ .open = pwm_open,
+ .read = seq_read,
+ .write = pwm_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const struct file_operations pwm_operations_raw = {
+ .owner = THIS_MODULE,
+ .open = pwm_open,
+ .read = seq_read,
+ .write = pwm_write_raw,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void init_debugfs(void)
+{
+ (void) debugfs_create_file("pwm_if", S_IFREG | S_IRUGO | S_IWUGO,
+ NULL, NULL, &pwm_operations);
+ (void) debugfs_create_file("pwm_raw", S_IFREG | S_IRUGO | S_IWUGO,
+ NULL, NULL, &pwm_operations_raw);
+}
+#endif
+
+module_init(pwm_init);
+
+static void __exit pwm_exit(void)
+{
+ platform_driver_unregister(&pwm_driver);
+}
+module_exit(pwm_exit);
+
+MODULE_LICENSE("GPL v2");