* [[RFC] 5/5] Incorporate PWM API code into KBuild
From: Bill Gatliff @ 2009-10-19 20:32 UTC (permalink / raw)
To: linux-embedded; +Cc: Bill Gatliff
In-Reply-To: <1255984366-26952-5-git-send-email-bgat@billgatliff.com>
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
drivers/Kconfig | 2 ++
drivers/Makefile | 2 ++
drivers/pwm/Kconfig | 30 ++++++++++++++++++++++++++++++
drivers/pwm/Makefile | 7 +++++++
4 files changed, 41 insertions(+), 0 deletions(-)
create mode 100644 drivers/pwm/Kconfig
create mode 100644 drivers/pwm/Makefile
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 48bbdbe..ee45cf7 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -56,6 +56,8 @@ source "drivers/pps/Kconfig"
source "drivers/gpio/Kconfig"
+source "drivers/pwm/Kconfig"
+
source "drivers/w1/Kconfig"
source "drivers/power/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index 6ee53c7..e6143f3 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -6,6 +6,8 @@
#
obj-y += gpio/
+obj-$(CONFIG_GENERIC_PWM) += pwm/
+
obj-$(CONFIG_PCI) += pci/
obj-$(CONFIG_PARISC) += parisc/
obj-$(CONFIG_RAPIDIO) += rapidio/
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
new file mode 100644
index 0000000..0ead279
--- /dev/null
+++ b/drivers/pwm/Kconfig
@@ -0,0 +1,30 @@
+#
+# PWM infrastructure and devices
+#
+
+menuconfig GENERIC_PWM
+ tristate "PWM Support"
+ help
+ This enables PWM support through the generic PWM library.
+ If unsure, say N.
+
+if GENERIC_PWM
+
+config ATMEL_PWM
+ tristate "Atmel AT32/AT91 PWM support"
+ depends on AVR32 || ARCH_AT91
+ help
+ This option enables device driver support for the PWMC
+ peripheral channels found on certain Atmel processors.
+ Pulse Width Modulation is used many for purposes, including
+ software controlled power-efficent backlights on LCD
+ displays, motor control, and waveform generation. If
+ unsure, say N.
+
+config GPIO_PWM
+ tristate "PWM emulation using GPIO"
+ help
+ This option enables a single-channel PWM device using
+ a kernel interval timer and a GPIO pin. If unsure, say N.
+
+endif
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
new file mode 100644
index 0000000..e42246b
--- /dev/null
+++ b/drivers/pwm/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for pwm devices
+#
+obj-y := pwm.o
+
+obj-$(CONFIG_ATMEL_PWM) += atmel-pwm.o
+obj-$(CONFIG_GPIO_PWM) += gpio.o
\ No newline at end of file
--
1.6.3.3
^ permalink raw reply related
* [[RFC] 4/5] An LED "dimmer" trigger, which uses the PWM API to vary the brightness of an LED according to system load
From: Bill Gatliff @ 2009-10-19 20:32 UTC (permalink / raw)
To: linux-embedded; +Cc: Bill Gatliff
In-Reply-To: <1255984366-26952-4-git-send-email-bgat@billgatliff.com>
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
drivers/leds/Kconfig | 32 ++++++-
drivers/leds/Makefile | 3 +
drivers/leds/leds-pwm.c | 224 +++++++++++++++++++++++---------------------
drivers/leds/ledtrig-dim.c | 95 +++++++++++++++++++
include/linux/pwm-led.h | 34 +++++++
5 files changed, 278 insertions(+), 110 deletions(-)
create mode 100644 drivers/leds/ledtrig-dim.c
create mode 100644 include/linux/pwm-led.h
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index e4f599f..b39c863 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -17,12 +17,12 @@ config LEDS_CLASS
comment "LED drivers"
-config LEDS_ATMEL_PWM
- tristate "LED Support using Atmel PWM outputs"
- depends on LEDS_CLASS && ATMEL_PWM
+config LEDS_CORGI
+ tristate "LED Support for the Sharp SL-C7x0 series"
+ depends on LEDS_CLASS && PXA_SHARP_C7xx
help
- This option enables support for LEDs driven using outputs
- of the dedicated PWM controller found on newer Atmel SOCs.
+ This option enables support for the LEDs on Sharp Zaurus
+ SL-C7x0 series (C700, C750, C760, C860).
config LEDS_LOCOMO
tristate "LED Support for Locomo device"
@@ -141,6 +141,20 @@ config LEDS_GPIO_OF
bool "OpenFirmware platform device bindings for GPIO LEDs"
depends on LEDS_GPIO && OF_DEVICE
default y
+
+config LEDS_HP_DISK
+ tristate "LED Support for disk protection LED on HP notebooks"
+ depends on LEDS_CLASS && ACPI
+
+config LEDS_PWM
+ tristate "LED Support for PWM connected LEDs"
+ depends on LEDS_CLASS && GENERIC_PWM
+ help
+ Enables support for LEDs connected to PWM outputs.
+
+config LEDS_CM_X270
+ tristate "LED Support for the CM-X270 LEDs"
+ depends on LEDS_CLASS && MACH_ARMCORE
help
Let the leds-gpio driver drive LEDs which have been defined as
of_platform devices. For instance, LEDs which are listed in a "dts"
@@ -263,6 +277,14 @@ config LEDS_TRIGGER_IDE_DISK
This allows LEDs to be controlled by IDE disk activity.
If unsure, say Y.
+config LEDS_TRIGGER_DIM
+ tristate "LED Dimmer Trigger"
+ depends on LEDS_TRIGGERS
+ help
+ Regulates the brightness of an LED based on the 1-minute CPU
+ load average. Ideal for PWM-driven LEDs.
+ If unsure, say Y.
+
config LEDS_TRIGGER_HEARTBEAT
tristate "LED Heartbeat Trigger"
depends on LEDS_TRIGGERS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 46d7270..f4ff10b 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -21,6 +21,8 @@ obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o
obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o
obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o
obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o
+obj-$(CONFIG_LEDS_PWM) += leds-pwm.o
+obj-$(CONFIG_LEDS_CM_X270) += leds-cm-x270.o
obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o
obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o
obj-$(CONFIG_LEDS_FSG) += leds-fsg.o
@@ -36,6 +38,7 @@ obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
# LED Triggers
obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o
obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK) += ledtrig-ide-disk.o
+obj-$(CONFIG_LEDS_TRIGGER_DIM) += ledtrig-dim.o
obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT) += ledtrig-heartbeat.o
obj-$(CONFIG_LEDS_TRIGGER_BACKLIGHT) += ledtrig-backlight.o
obj-$(CONFIG_LEDS_TRIGGER_GPIO) += ledtrig-gpio.o
diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c
index cdfdc87..3103dc3 100644
--- a/drivers/leds/leds-pwm.c
+++ b/drivers/leds/leds-pwm.c
@@ -1,153 +1,167 @@
-/*
- * linux/drivers/leds-pwm.c
- *
- * simple PWM based LED control
- *
- * Copyright 2009 Luotao Fu @ Pengutronix (l.fu@pengutronix.de)
- *
- * based on leds-gpio.c by Raphael Assenat <raph@8d.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/module.h>
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/platform_device.h>
-#include <linux/fb.h>
#include <linux/leds.h>
-#include <linux/err.h>
+#include <linux/io.h>
#include <linux/pwm.h>
-#include <linux/leds_pwm.h>
-
-struct led_pwm_data {
- struct led_classdev cdev;
- struct pwm_device *pwm;
- unsigned int active_low;
- unsigned int period;
- unsigned int max_brightness;
+#include <linux/pwm-led.h>
+
+
+struct led_pwm {
+ struct led_classdev led;
+ struct pwm_channel *pwm;
+ int percent;
};
-static void led_pwm_set(struct led_classdev *led_cdev,
- enum led_brightness brightness)
+
+static void
+led_pwm_brightness_set(struct led_classdev *c,
+ enum led_brightness b)
+{
+ struct led_pwm *led;
+ int percent;
+
+ percent = (b * 100) / (LED_FULL - LED_OFF);
+ led = container_of(c, struct led_pwm, led);
+ led->percent = percent;
+ pwm_set_duty_percent(led->pwm, percent);
+}
+
+
+static enum led_brightness
+led_pwm_brightness_get(struct led_classdev *c)
{
- struct led_pwm_data *led_dat =
- container_of(led_cdev, struct led_pwm_data, cdev);
- unsigned int max = led_dat->max_brightness;
- unsigned int period = led_dat->period;
-
- if (brightness == 0) {
- pwm_config(led_dat->pwm, 0, period);
- pwm_disable(led_dat->pwm);
- } else {
- pwm_config(led_dat->pwm, brightness * period / max, period);
- pwm_enable(led_dat->pwm);
+ struct led_pwm *led;
+ led = container_of(c, struct led_pwm, led);
+ return led->percent;
+}
+
+
+static int
+led_pwm_blink_set(struct led_classdev *c,
+ unsigned long *on_ms,
+ unsigned long *off_ms)
+{
+ struct led_pwm *led;
+ struct pwm_channel_config cfg;
+
+ led = container_of(c, struct led_pwm, led);
+
+ if (*on_ms == 0 && *off_ms == 0) {
+ *on_ms = 1000UL;
+ *off_ms = 1000UL;
}
+
+ cfg.config_mask = PWM_CONFIG_DUTY_NS
+ | PWM_CONFIG_PERIOD_NS;
+
+ cfg.duty_ns = *on_ms * 1000000UL;
+ cfg.period_ns = (*on_ms + *off_ms) * 1000000UL;
+
+ return pwm_config(led->pwm, &cfg);
}
-static int led_pwm_probe(struct platform_device *pdev)
+
+static int __init
+led_pwm_probe(struct platform_device *pdev)
{
- struct led_pwm_platform_data *pdata = pdev->dev.platform_data;
- struct led_pwm *cur_led;
- struct led_pwm_data *leds_data, *led_dat;
- int i, ret = 0;
+ struct pwm_led_platform_data *pdata = pdev->dev.platform_data;
+ struct led_pwm *led;
+ struct device *d = &pdev->dev;
+ int ret;
- if (!pdata)
- return -EBUSY;
+ if (!pdata || !pdata->led_info)
+ return -EINVAL;
- leds_data = kzalloc(sizeof(struct led_pwm_data) * pdata->num_leds,
- GFP_KERNEL);
- if (!leds_data)
+ if (!try_module_get(d->driver->owner))
+ return -ENODEV;
+
+ led = kzalloc(sizeof(*led), GFP_KERNEL);
+ if (!led)
return -ENOMEM;
- for (i = 0; i < pdata->num_leds; i++) {
- cur_led = &pdata->leds[i];
- led_dat = &leds_data[i];
-
- led_dat->pwm = pwm_request(cur_led->pwm_id,
- cur_led->name);
- if (IS_ERR(led_dat->pwm)) {
- dev_err(&pdev->dev, "unable to request PWM %d\n",
- cur_led->pwm_id);
- goto err;
- }
-
- led_dat->cdev.name = cur_led->name;
- led_dat->cdev.default_trigger = cur_led->default_trigger;
- led_dat->active_low = cur_led->active_low;
- led_dat->max_brightness = cur_led->max_brightness;
- led_dat->period = cur_led->pwm_period_ns;
- led_dat->cdev.brightness_set = led_pwm_set;
- led_dat->cdev.brightness = LED_OFF;
- led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
-
- ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
- if (ret < 0) {
- pwm_free(led_dat->pwm);
- goto err;
- }
+ led->pwm = pwm_request(pdata->bus_id, pdata->chan,
+ pdata->led_info->name);
+ if (!led->pwm) {
+ ret = -EINVAL;
+ goto err_pwm_request;
}
- platform_set_drvdata(pdev, leds_data);
+ platform_set_drvdata(pdev, led);
- return 0;
+ led->led.name = pdata->led_info->name;
+ led->led.default_trigger = pdata->led_info->default_trigger;
+ led->led.brightness_set = led_pwm_brightness_set;
+ led->led.brightness_get = led_pwm_brightness_get;
+ led->led.blink_set = led_pwm_blink_set;
+ led->led.brightness = LED_OFF;
-err:
- if (i > 0) {
- for (i = i - 1; i >= 0; i--) {
- led_classdev_unregister(&leds_data[i].cdev);
- pwm_free(leds_data[i].pwm);
- }
- }
+ ret = pwm_config(led->pwm, pdata->config);
+ if (ret)
+ goto err_pwm_config;
+ pwm_start(led->pwm);
- kfree(leds_data);
+ ret = led_classdev_register(&pdev->dev, &led->led);
+ if (ret < 0)
+ goto err_classdev_register;
+
+ return 0;
+
+err_classdev_register:
+ pwm_stop(led->pwm);
+err_pwm_config:
+ pwm_free(led->pwm);
+err_pwm_request:
+ kfree(led);
return ret;
}
-static int __devexit led_pwm_remove(struct platform_device *pdev)
+
+static int
+led_pwm_remove(struct platform_device *pdev)
{
- int i;
- struct led_pwm_platform_data *pdata = pdev->dev.platform_data;
- struct led_pwm_data *leds_data;
+ struct led_pwm *led = platform_get_drvdata(pdev);
+ struct device *d = &pdev->dev;
- leds_data = platform_get_drvdata(pdev);
+ led_classdev_unregister(&led->led);
- for (i = 0; i < pdata->num_leds; i++) {
- led_classdev_unregister(&leds_data[i].cdev);
- pwm_free(leds_data[i].pwm);
+ if (led->pwm) {
+ pwm_stop(led->pwm);
+ pwm_free(led->pwm);
}
- kfree(leds_data);
+ kfree(led);
+ module_put(d->driver->owner);
return 0;
}
+
static struct platform_driver led_pwm_driver = {
- .probe = led_pwm_probe,
- .remove = __devexit_p(led_pwm_remove),
- .driver = {
- .name = "leds_pwm",
- .owner = THIS_MODULE,
+ .driver = {
+ .name = "leds-pwm",
+ .owner = THIS_MODULE,
},
+ .probe = led_pwm_probe,
+ .remove = led_pwm_remove,
};
-static int __init led_pwm_init(void)
+
+static int __init led_pwm_modinit(void)
{
return platform_driver_register(&led_pwm_driver);
}
+module_init(led_pwm_modinit);
+
-static void __exit led_pwm_exit(void)
+static void __exit led_pwm_modexit(void)
{
platform_driver_unregister(&led_pwm_driver);
}
+module_exit(led_pwm_modexit);
-module_init(led_pwm_init);
-module_exit(led_pwm_exit);
-MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>");
-MODULE_DESCRIPTION("PWM LED driver for PXA");
+MODULE_AUTHOR("Bill Gatliff <bgat@billgatliff.com>");
+MODULE_DESCRIPTION("Driver for LEDs with PWM-controlled brightness");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:leds-pwm");
diff --git a/drivers/leds/ledtrig-dim.c b/drivers/leds/ledtrig-dim.c
new file mode 100644
index 0000000..299865b
--- /dev/null
+++ b/drivers/leds/ledtrig-dim.c
@@ -0,0 +1,95 @@
+/*
+ * LED Dim Trigger
+ *
+ * Copyright (C) 2008 Bill Gatliff <bgat@billgatliff.com>
+ *
+ * "Dims" an LED based on system load. Derived from Atsushi Nemoto's
+ * ledtrig-heartbeat.c.
+ *
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/timer.h>
+#include <linux/sched.h>
+#include <linux/leds.h>
+
+#include "leds.h"
+
+struct dim_trig_data {
+ struct timer_list timer;
+};
+
+
+static void
+led_dim_function(unsigned long data)
+{
+ struct led_classdev *led_cdev = (struct led_classdev *)data;
+ struct dim_trig_data *dim_data = led_cdev->trigger_data;
+ unsigned int brightness;
+
+ brightness = ((LED_FULL - LED_OFF) * avenrun[0]) / EXP_1;
+ if (brightness > LED_FULL)
+ brightness = LED_FULL;
+
+ led_set_brightness(led_cdev, brightness);
+ mod_timer(&dim_data->timer, jiffies + msecs_to_jiffies(500));
+}
+
+
+static void
+dim_trig_activate(struct led_classdev *led_cdev)
+{
+ struct dim_trig_data *dim_data;
+
+ dim_data = kzalloc(sizeof(*dim_data), GFP_KERNEL);
+ if (!dim_data)
+ return;
+
+ led_cdev->trigger_data = dim_data;
+ setup_timer(&dim_data->timer,
+ led_dim_function, (unsigned long)led_cdev);
+ led_dim_function(dim_data->timer.data);
+}
+
+
+static void
+dim_trig_deactivate(struct led_classdev *led_cdev)
+{
+ struct dim_trig_data *dim_data = led_cdev->trigger_data;
+
+ if (dim_data) {
+ del_timer_sync(&dim_data->timer);
+ kfree(dim_data);
+ }
+}
+
+
+static struct led_trigger dim_led_trigger = {
+ .name = "dim",
+ .activate = dim_trig_activate,
+ .deactivate = dim_trig_deactivate,
+};
+
+
+static int __init dim_trig_init(void)
+{
+ return led_trigger_register(&dim_led_trigger);
+}
+module_init(dim_trig_init);
+
+
+static void __exit dim_trig_exit(void)
+{
+ led_trigger_unregister(&dim_led_trigger);
+}
+module_exit(dim_trig_exit);
+
+
+MODULE_AUTHOR("Bill Gatliff <bgat@billgatliff.com>");
+MODULE_DESCRIPTION("Dim LED trigger");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/pwm-led.h b/include/linux/pwm-led.h
new file mode 100644
index 0000000..92363c8
--- /dev/null
+++ b/include/linux/pwm-led.h
@@ -0,0 +1,34 @@
+#ifndef __LINUX_PWM_LED_H
+#define __LINUX_PWM_LED_H
+
+/*
+ * include/linux/pwm-led.h
+ *
+ * Copyright (C) 2008 Bill Gatliff
+ *
+ * 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.
+ *
+ * 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
+ */
+
+struct led_info;
+struct pwm_channel_config;
+
+struct pwm_led_platform_data {
+ const char *bus_id;
+ int chan;
+ struct pwm_channel_config *config;
+ struct led_info *led_info;
+};
+
+#endif /* __LINUX_PWM_LED_H */
--
1.6.3.3
^ permalink raw reply related
* [[RFC] 3/5] Expunge old Atmel PWMC driver, replacing it with one that conforms to the PWM API
From: Bill Gatliff @ 2009-10-19 20:32 UTC (permalink / raw)
To: linux-embedded; +Cc: Bill Gatliff
In-Reply-To: <1255984366-26952-3-git-send-email-bgat@billgatliff.com>
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
drivers/misc/Makefile | 6 +-
drivers/misc/atmel_pwm.c | 409 ------------------------------
drivers/pwm/atmel-pwm.c | 633 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 638 insertions(+), 410 deletions(-)
delete mode 100644 drivers/misc/atmel_pwm.c
create mode 100644 drivers/pwm/atmel-pwm.c
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index f982d2e..35f13c2 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -4,7 +4,11 @@
obj-$(CONFIG_IBM_ASM) += ibmasm/
obj-$(CONFIG_HDPU_FEATURES) += hdpuftrs/
-obj-$(CONFIG_ATMEL_PWM) += atmel_pwm.o
+obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o
+obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o
+obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o
+obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o
+obj-$(CONFIG_ACER_WMI) += acer-wmi.o
obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o
obj-$(CONFIG_ATMEL_TCLIB) += atmel_tclib.o
obj-$(CONFIG_ICS932S401) += ics932s401.o
diff --git a/drivers/misc/atmel_pwm.c b/drivers/misc/atmel_pwm.c
deleted file mode 100644
index 6aa5294..0000000
--- a/drivers/misc/atmel_pwm.c
+++ /dev/null
@@ -1,409 +0,0 @@
-#include <linux/module.h>
-#include <linux/clk.h>
-#include <linux/err.h>
-#include <linux/io.h>
-#include <linux/interrupt.h>
-#include <linux/platform_device.h>
-#include <linux/atmel_pwm.h>
-
-
-/*
- * This is a simple driver for the PWM controller found in various newer
- * Atmel SOCs, including the AVR32 series and the AT91sam9263.
- *
- * Chips with current Linux ports have only 4 PWM channels, out of max 32.
- * AT32UC3A and AT32UC3B chips have 7 channels (but currently no Linux).
- * Docs are inconsistent about the width of the channel counter registers;
- * it's at least 16 bits, but several places say 20 bits.
- */
-#define PWM_NCHAN 4 /* max 32 */
-
-struct pwm {
- spinlock_t lock;
- struct platform_device *pdev;
- u32 mask;
- int irq;
- void __iomem *base;
- struct clk *clk;
- struct pwm_channel *channel[PWM_NCHAN];
- void (*handler[PWM_NCHAN])(struct pwm_channel *);
-};
-
-
-/* global PWM controller registers */
-#define PWM_MR 0x00
-#define PWM_ENA 0x04
-#define PWM_DIS 0x08
-#define PWM_SR 0x0c
-#define PWM_IER 0x10
-#define PWM_IDR 0x14
-#define PWM_IMR 0x18
-#define PWM_ISR 0x1c
-
-static inline void pwm_writel(const struct pwm *p, unsigned offset, u32 val)
-{
- __raw_writel(val, p->base + offset);
-}
-
-static inline u32 pwm_readl(const struct pwm *p, unsigned offset)
-{
- return __raw_readl(p->base + offset);
-}
-
-static inline void __iomem *pwmc_regs(const struct pwm *p, int index)
-{
- return p->base + 0x200 + index * 0x20;
-}
-
-static struct pwm *pwm;
-
-static void pwm_dumpregs(struct pwm_channel *ch, char *tag)
-{
- struct device *dev = &pwm->pdev->dev;
-
- dev_dbg(dev, "%s: mr %08x, sr %08x, imr %08x\n",
- tag,
- pwm_readl(pwm, PWM_MR),
- pwm_readl(pwm, PWM_SR),
- pwm_readl(pwm, PWM_IMR));
- dev_dbg(dev,
- "pwm ch%d - mr %08x, dty %u, prd %u, cnt %u\n",
- ch->index,
- pwm_channel_readl(ch, PWM_CMR),
- pwm_channel_readl(ch, PWM_CDTY),
- pwm_channel_readl(ch, PWM_CPRD),
- pwm_channel_readl(ch, PWM_CCNT));
-}
-
-
-/**
- * pwm_channel_alloc - allocate an unused PWM channel
- * @index: identifies the channel
- * @ch: structure to be initialized
- *
- * Drivers allocate PWM channels according to the board's wiring, and
- * matching board-specific setup code. Returns zero or negative errno.
- */
-int pwm_channel_alloc(int index, struct pwm_channel *ch)
-{
- unsigned long flags;
- int status = 0;
-
- /* insist on PWM init, with this signal pinned out */
- if (!pwm || !(pwm->mask & 1 << index))
- return -ENODEV;
-
- if (index < 0 || index >= PWM_NCHAN || !ch)
- return -EINVAL;
- memset(ch, 0, sizeof *ch);
-
- spin_lock_irqsave(&pwm->lock, flags);
- if (pwm->channel[index])
- status = -EBUSY;
- else {
- clk_enable(pwm->clk);
-
- ch->regs = pwmc_regs(pwm, index);
- ch->index = index;
-
- /* REVISIT: ap7000 seems to go 2x as fast as we expect!! */
- ch->mck = clk_get_rate(pwm->clk);
-
- pwm->channel[index] = ch;
- pwm->handler[index] = NULL;
-
- /* channel and irq are always disabled when we return */
- pwm_writel(pwm, PWM_DIS, 1 << index);
- pwm_writel(pwm, PWM_IDR, 1 << index);
- }
- spin_unlock_irqrestore(&pwm->lock, flags);
- return status;
-}
-EXPORT_SYMBOL(pwm_channel_alloc);
-
-static int pwmcheck(struct pwm_channel *ch)
-{
- int index;
-
- if (!pwm)
- return -ENODEV;
- if (!ch)
- return -EINVAL;
- index = ch->index;
- if (index < 0 || index >= PWM_NCHAN || pwm->channel[index] != ch)
- return -EINVAL;
-
- return index;
-}
-
-/**
- * pwm_channel_free - release a previously allocated channel
- * @ch: the channel being released
- *
- * The channel is completely shut down (counter and IRQ disabled),
- * and made available for re-use. Returns zero, or negative errno.
- */
-int pwm_channel_free(struct pwm_channel *ch)
-{
- unsigned long flags;
- int t;
-
- spin_lock_irqsave(&pwm->lock, flags);
- t = pwmcheck(ch);
- if (t >= 0) {
- pwm->channel[t] = NULL;
- pwm->handler[t] = NULL;
-
- /* channel and irq are always disabled when we return */
- pwm_writel(pwm, PWM_DIS, 1 << t);
- pwm_writel(pwm, PWM_IDR, 1 << t);
-
- clk_disable(pwm->clk);
- t = 0;
- }
- spin_unlock_irqrestore(&pwm->lock, flags);
- return t;
-}
-EXPORT_SYMBOL(pwm_channel_free);
-
-int __pwm_channel_onoff(struct pwm_channel *ch, int enabled)
-{
- unsigned long flags;
- int t;
-
- /* OMITTED FUNCTIONALITY: starting several channels in synch */
-
- spin_lock_irqsave(&pwm->lock, flags);
- t = pwmcheck(ch);
- if (t >= 0) {
- pwm_writel(pwm, enabled ? PWM_ENA : PWM_DIS, 1 << t);
- t = 0;
- pwm_dumpregs(ch, enabled ? "enable" : "disable");
- }
- spin_unlock_irqrestore(&pwm->lock, flags);
-
- return t;
-}
-EXPORT_SYMBOL(__pwm_channel_onoff);
-
-/**
- * pwm_clk_alloc - allocate and configure CLKA or CLKB
- * @prescale: from 0..10, the power of two used to divide MCK
- * @div: from 1..255, the linear divisor to use
- *
- * Returns PWM_CPR_CLKA, PWM_CPR_CLKB, or negative errno. The allocated
- * clock will run with a period of (2^prescale * div) / MCK, or twice as
- * long if center aligned PWM output is used. The clock must later be
- * deconfigured using pwm_clk_free().
- */
-int pwm_clk_alloc(unsigned prescale, unsigned div)
-{
- unsigned long flags;
- u32 mr;
- u32 val = (prescale << 8) | div;
- int ret = -EBUSY;
-
- if (prescale >= 10 || div == 0 || div > 255)
- return -EINVAL;
-
- spin_lock_irqsave(&pwm->lock, flags);
- mr = pwm_readl(pwm, PWM_MR);
- if ((mr & 0xffff) == 0) {
- mr |= val;
- ret = PWM_CPR_CLKA;
- } else if ((mr & (0xffff << 16)) == 0) {
- mr |= val << 16;
- ret = PWM_CPR_CLKB;
- }
- if (ret > 0)
- pwm_writel(pwm, PWM_MR, mr);
- spin_unlock_irqrestore(&pwm->lock, flags);
- return ret;
-}
-EXPORT_SYMBOL(pwm_clk_alloc);
-
-/**
- * pwm_clk_free - deconfigure and release CLKA or CLKB
- *
- * Reverses the effect of pwm_clk_alloc().
- */
-void pwm_clk_free(unsigned clk)
-{
- unsigned long flags;
- u32 mr;
-
- spin_lock_irqsave(&pwm->lock, flags);
- mr = pwm_readl(pwm, PWM_MR);
- if (clk == PWM_CPR_CLKA)
- pwm_writel(pwm, PWM_MR, mr & ~(0xffff << 0));
- if (clk == PWM_CPR_CLKB)
- pwm_writel(pwm, PWM_MR, mr & ~(0xffff << 16));
- spin_unlock_irqrestore(&pwm->lock, flags);
-}
-EXPORT_SYMBOL(pwm_clk_free);
-
-/**
- * pwm_channel_handler - manage channel's IRQ handler
- * @ch: the channel
- * @handler: the handler to use, possibly NULL
- *
- * If the handler is non-null, the handler will be called after every
- * period of this PWM channel. If the handler is null, this channel
- * won't generate an IRQ.
- */
-int pwm_channel_handler(struct pwm_channel *ch,
- void (*handler)(struct pwm_channel *ch))
-{
- unsigned long flags;
- int t;
-
- spin_lock_irqsave(&pwm->lock, flags);
- t = pwmcheck(ch);
- if (t >= 0) {
- pwm->handler[t] = handler;
- pwm_writel(pwm, handler ? PWM_IER : PWM_IDR, 1 << t);
- t = 0;
- }
- spin_unlock_irqrestore(&pwm->lock, flags);
-
- return t;
-}
-EXPORT_SYMBOL(pwm_channel_handler);
-
-static irqreturn_t pwm_irq(int id, void *_pwm)
-{
- struct pwm *p = _pwm;
- irqreturn_t handled = IRQ_NONE;
- u32 irqstat;
- int index;
-
- spin_lock(&p->lock);
-
- /* ack irqs, then handle them */
- irqstat = pwm_readl(pwm, PWM_ISR);
-
- while (irqstat) {
- struct pwm_channel *ch;
- void (*handler)(struct pwm_channel *ch);
-
- index = ffs(irqstat) - 1;
- irqstat &= ~(1 << index);
- ch = pwm->channel[index];
- handler = pwm->handler[index];
- if (handler && ch) {
- spin_unlock(&p->lock);
- handler(ch);
- spin_lock(&p->lock);
- handled = IRQ_HANDLED;
- }
- }
-
- spin_unlock(&p->lock);
- return handled;
-}
-
-static int __init pwm_probe(struct platform_device *pdev)
-{
- struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- int irq = platform_get_irq(pdev, 0);
- u32 *mp = pdev->dev.platform_data;
- struct pwm *p;
- int status = -EIO;
-
- if (pwm)
- return -EBUSY;
- if (!r || irq < 0 || !mp || !*mp)
- return -ENODEV;
- if (*mp & ~((1<<PWM_NCHAN)-1)) {
- dev_warn(&pdev->dev, "mask 0x%x ... more than %d channels\n",
- *mp, PWM_NCHAN);
- return -EINVAL;
- }
-
- p = kzalloc(sizeof(*p), GFP_KERNEL);
- if (!p)
- return -ENOMEM;
-
- spin_lock_init(&p->lock);
- p->pdev = pdev;
- p->mask = *mp;
- p->irq = irq;
- p->base = ioremap(r->start, r->end - r->start + 1);
- if (!p->base)
- goto fail;
- p->clk = clk_get(&pdev->dev, "pwm_clk");
- if (IS_ERR(p->clk)) {
- status = PTR_ERR(p->clk);
- p->clk = NULL;
- goto fail;
- }
-
- status = request_irq(irq, pwm_irq, 0, pdev->name, p);
- if (status < 0)
- goto fail;
-
- pwm = p;
- platform_set_drvdata(pdev, p);
-
- return 0;
-
-fail:
- if (p->clk)
- clk_put(p->clk);
- if (p->base)
- iounmap(p->base);
-
- kfree(p);
- return status;
-}
-
-static int __exit pwm_remove(struct platform_device *pdev)
-{
- struct pwm *p = platform_get_drvdata(pdev);
-
- if (p != pwm)
- return -EINVAL;
-
- clk_enable(pwm->clk);
- pwm_writel(pwm, PWM_DIS, (1 << PWM_NCHAN) - 1);
- pwm_writel(pwm, PWM_IDR, (1 << PWM_NCHAN) - 1);
- clk_disable(pwm->clk);
-
- pwm = NULL;
-
- free_irq(p->irq, p);
- clk_put(p->clk);
- iounmap(p->base);
- kfree(p);
-
- return 0;
-}
-
-static struct platform_driver atmel_pwm_driver = {
- .driver = {
- .name = "atmel_pwm",
- .owner = THIS_MODULE,
- },
- .remove = __exit_p(pwm_remove),
-
- /* NOTE: PWM can keep running in AVR32 "idle" and "frozen" states;
- * and all AT91sam9263 states, albeit at reduced clock rate if
- * MCK becomes the slow clock (i.e. what Linux labels STR).
- */
-};
-
-static int __init pwm_init(void)
-{
- return platform_driver_probe(&atmel_pwm_driver, pwm_probe);
-}
-module_init(pwm_init);
-
-static void __exit pwm_exit(void)
-{
- platform_driver_unregister(&atmel_pwm_driver);
-}
-module_exit(pwm_exit);
-
-MODULE_DESCRIPTION("Driver for AT32/AT91 PWM module");
-MODULE_LICENSE("GPL");
-MODULE_ALIAS("platform:atmel_pwm");
diff --git a/drivers/pwm/atmel-pwm.c b/drivers/pwm/atmel-pwm.c
new file mode 100644
index 0000000..e1b3655
--- /dev/null
+++ b/drivers/pwm/atmel-pwm.c
@@ -0,0 +1,633 @@
+/*
+ * drivers/pwm/atmel-pwm.c
+ *
+ * Copyright (C) 2008 Bill Gatliff
+ * Copyright (C) 2007 David Brownell
+ *
+ * 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.
+ *
+ * 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/init.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+
+enum {
+ /* registers common to the PWMC peripheral */
+ PWMC_MR = 0,
+ PWMC_ENA = 4,
+ PWMC_DIS = 8,
+ PWMC_SR = 0xc,
+ PWMC_IER = 0x10,
+ PWMC_IDR = 0x14,
+ PWMC_IMR = 0x18,
+ PWMC_ISR = 0x1c,
+
+ /* registers per each PWMC channel */
+ PWMC_CMR = 0,
+ PWMC_CDTY = 4,
+ PWMC_CPRD = 8,
+ PWMC_CCNT = 0xc,
+ PWMC_CUPD = 0x10,
+
+ /* how to find each channel */
+ PWMC_CHAN_BASE = 0x200,
+ PWMC_CHAN_STRIDE = 0x20,
+
+ /* CMR bits of interest */
+ PWMC_CMR_CPD = 10,
+ PWMC_CMR_CPOL = 9,
+ PWMC_CMR_CALG = 8,
+ PWMC_CMR_CPRE_MASK = 0xf,
+};
+
+struct atmel_pwm {
+ struct pwm_device pwm;
+ spinlock_t lock;
+ void __iomem *iobase;
+ struct clk *clk;
+ u32 *sync_mask;
+ int irq;
+ u32 ccnt_mask;
+};
+
+
+static inline void
+pwmc_writel(const struct atmel_pwm *p,
+ unsigned offset, u32 val)
+{
+ __raw_writel(val, p->iobase + offset);
+}
+
+
+static inline u32
+pwmc_readl(const struct atmel_pwm *p,
+ unsigned offset)
+{
+ return __raw_readl(p->iobase + offset);
+}
+
+
+static inline void
+pwmc_chan_writel(const struct pwm_channel *p,
+ u32 offset, u32 val)
+{
+ const struct atmel_pwm *ap
+ = container_of(p->pwm, struct atmel_pwm, pwm);
+
+ if (PWMC_CMR == offset)
+ val &= ((1 << PWMC_CMR_CPD)
+ | (1 << PWMC_CMR_CPOL)
+ | (1 << PWMC_CMR_CALG)
+ | (PWMC_CMR_CPRE_MASK));
+ else
+ val &= ap->ccnt_mask;
+
+ pwmc_writel(ap, offset + PWMC_CHAN_BASE
+ + (p->chan * PWMC_CHAN_STRIDE), val);
+}
+
+
+static inline u32
+pwmc_chan_readl(const struct pwm_channel *p,
+ u32 offset)
+{
+ const struct atmel_pwm *ap
+ = container_of(p->pwm, struct atmel_pwm, pwm);
+
+ return pwmc_readl(ap, offset + PWMC_CHAN_BASE
+ + (p->chan * PWMC_CHAN_STRIDE));
+}
+
+
+static inline int
+__atmel_pwm_is_on(struct pwm_channel *p)
+{
+ struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+ return (pwmc_readl(ap, PWMC_SR) & (1 << p->chan)) ? 1 : 0;
+}
+
+
+static inline void
+__atmel_pwm_unsynchronize(struct pwm_channel *p,
+ struct pwm_channel *to_p)
+{
+ const struct atmel_pwm *ap
+ = container_of(p->pwm, struct atmel_pwm, pwm);
+ int wchan;
+
+ if (to_p) {
+ ap->sync_mask[p->chan] &= ~(1 << to_p->chan);
+ ap->sync_mask[to_p->chan] &= ~(1 << p->chan);
+ goto done;
+ }
+
+ ap->sync_mask[p->chan] = 0;
+ for (wchan = 0; wchan < ap->pwm.nchan; wchan++)
+ ap->sync_mask[wchan] &= ~(1 << p->chan);
+done:
+ pr_debug("%s:%d sync_mask %x\n",
+ p->pwm->bus_id, p->chan, ap->sync_mask[p->chan]);
+}
+
+
+static inline void
+__atmel_pwm_synchronize(struct pwm_channel *p,
+ struct pwm_channel *to_p)
+{
+ const struct atmel_pwm *ap
+ = container_of(p->pwm, struct atmel_pwm, pwm);
+
+ if (!to_p)
+ return;
+
+ ap->sync_mask[p->chan] |= (1 << to_p->chan);
+ ap->sync_mask[to_p->chan] |= (1 << p->chan);
+
+ pr_debug("%s:%d sync_mask %x\n",
+ p->pwm->bus_id, p->chan, ap->sync_mask[p->chan]);
+}
+
+
+static inline void
+__atmel_pwm_stop(struct pwm_channel *p)
+{
+ struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+ u32 chid = 1 << p->chan;
+
+ pwmc_writel(ap, PWMC_DIS, ap->sync_mask[p->chan] | chid);
+}
+
+
+static inline void
+__atmel_pwm_start(struct pwm_channel *p)
+{
+ struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+ u32 chid = 1 << p->chan;
+
+ pwmc_writel(ap, PWMC_ENA, ap->sync_mask[p->chan] | chid);
+}
+
+
+static int
+atmel_pwm_synchronize(struct pwm_channel *p,
+ struct pwm_channel *to_p)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&p->lock, flags);
+ __atmel_pwm_synchronize(p, to_p);
+ spin_unlock_irqrestore(&p->lock, flags);
+ return 0;
+}
+
+
+static int
+atmel_pwm_unsynchronize(struct pwm_channel *p,
+ struct pwm_channel *from_p)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&p->lock, flags);
+ __atmel_pwm_unsynchronize(p, from_p);
+ spin_unlock_irqrestore(&p->lock, flags);
+ return 0;
+}
+
+
+static inline int
+__atmel_pwm_config_polarity(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ u32 cmr = pwmc_chan_readl(p, PWMC_CMR);
+
+ if (c->polarity)
+ cmr &= ~BIT(PWMC_CMR_CPOL);
+ else
+ cmr |= BIT(PWMC_CMR_CPOL);
+ pwmc_chan_writel(p, PWMC_CMR, cmr);
+
+ pr_debug("%s:%d polarity %d\n", p->pwm->bus_id,
+ p->chan, c->polarity);
+ return 0;
+}
+
+
+static inline int
+__atmel_pwm_config_duty_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ u32 cmr, cprd, cpre, cdty;
+
+ cmr = pwmc_chan_readl(p, PWMC_CMR);
+ cprd = pwmc_chan_readl(p, PWMC_CPRD);
+
+ cpre = cmr & PWMC_CMR_CPRE_MASK;
+ cmr &= ~BIT(PWMC_CMR_CPD);
+
+ cdty = cprd - (c->duty_ticks >> cpre);
+
+ p->duty_ticks = c->duty_ticks;
+
+ if (__atmel_pwm_is_on(p)) {
+ pwmc_chan_writel(p, PWMC_CMR, cmr);
+ pwmc_chan_writel(p, PWMC_CUPD, cdty);
+ } else
+ pwmc_chan_writel(p, PWMC_CDTY, cdty);
+
+ pr_debug("%s:%d duty_ticks = %lu cprd = %x"
+ " cdty = %x cpre = %x\n",
+ p->pwm->bus_id, p->chan, p->duty_ticks,
+ cprd, cdty, cpre);
+
+ return 0;
+}
+
+
+static inline int
+__atmel_pwm_config_period_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ u32 cmr, cprd, cpre;
+
+ cpre = fls(c->period_ticks);
+ if (cpre < 16)
+ cpre = 0;
+ else {
+ cpre -= 15;
+ if (cpre > 10)
+ return -EINVAL;
+ }
+
+ cmr = pwmc_chan_readl(p, PWMC_CMR);
+ cmr &= ~PWMC_CMR_CPRE_MASK;
+ cmr |= cpre;
+
+ cprd = c->period_ticks >> cpre;
+
+ pwmc_chan_writel(p, PWMC_CMR, cmr);
+ pwmc_chan_writel(p, PWMC_CPRD, cprd);
+ p->period_ticks = c->period_ticks;
+
+ pr_debug("%s:%d period_ticks = %lu cprd = %x cpre = %x\n",
+ p->pwm->bus_id, p->chan, p->period_ticks, cprd, cpre);
+
+ return 0;
+}
+
+
+
+static int
+atmel_pwm_config_nosleep(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ int ret = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&p->lock, flags);
+
+ switch (c->config_mask) {
+
+ case PWM_CONFIG_DUTY_TICKS:
+ __atmel_pwm_config_duty_ticks(p, c);
+ break;
+
+ case PWM_CONFIG_STOP:
+ __atmel_pwm_stop(p);
+ pr_debug("%s:%d stop\n", p->pwm->bus_id, p->chan);
+ break;
+
+ case PWM_CONFIG_START:
+ __atmel_pwm_start(p);
+ pr_debug("%s:%d start\n", p->pwm->bus_id, p->chan);
+ break;
+
+ case PWM_CONFIG_POLARITY:
+ __atmel_pwm_config_polarity(p, c);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ spin_unlock_irqrestore(&p->lock, flags);
+ return ret;
+}
+
+
+static int
+atmel_pwm_stop_sync(struct pwm_channel *p)
+{
+ struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+ int ret;
+ int was_on = __atmel_pwm_is_on(p);
+
+ if (was_on) {
+ do {
+ init_completion(&p->complete);
+ set_bit(FLAG_STOP, &p->flags);
+ pwmc_writel(ap, PWMC_IER, 1 << p->chan);
+
+ pr_debug("%s:%d waiting on stop_sync completion...\n",
+ p->pwm->bus_id, p->chan);
+
+ ret = wait_for_completion_interruptible(&p->complete);
+
+ pr_debug("%s:%d stop_sync complete (%d)\n",
+ p->pwm->bus_id, p->chan, ret);
+
+ if (ret)
+ return ret;
+ } while (p->flags & BIT(FLAG_STOP));
+ }
+
+ pr_debug("%s:%d stop_sync returning %d\n",
+ p->pwm->bus_id, p->chan, was_on);
+
+ return was_on;
+}
+
+
+static int
+atmel_pwm_config(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ int was_on = 0;
+
+ if (p->pwm->config_nosleep) {
+ if (!p->pwm->config_nosleep(p, c))
+ return 0;
+ }
+
+ might_sleep();
+
+ pr_debug("%s:%d config_mask %x\n",
+ p->pwm->bus_id, p->chan, c->config_mask);
+
+ was_on = atmel_pwm_stop_sync(p);
+ if (was_on < 0)
+ return was_on;
+
+ if (c->config_mask & PWM_CONFIG_PERIOD_TICKS) {
+ __atmel_pwm_config_period_ticks(p, c);
+ if (!(c->config_mask & PWM_CONFIG_DUTY_TICKS)) {
+ struct pwm_channel_config d = {
+ .config_mask = PWM_CONFIG_DUTY_TICKS,
+ .duty_ticks = p->duty_ticks,
+ };
+ __atmel_pwm_config_duty_ticks(p, &d);
+ }
+ }
+
+ if (c->config_mask & PWM_CONFIG_DUTY_TICKS)
+ __atmel_pwm_config_duty_ticks(p, c);
+
+ if (c->config_mask & PWM_CONFIG_POLARITY)
+ __atmel_pwm_config_polarity(p, c);
+
+ if ((c->config_mask & PWM_CONFIG_START)
+ || (was_on && !(c->config_mask & PWM_CONFIG_STOP)))
+ __atmel_pwm_start(p);
+
+ return 0;
+}
+
+
+static void
+__atmel_pwm_set_callback(struct pwm_channel *p,
+ pwm_callback_t callback)
+{
+ struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+
+ p->callback = callback;
+ pwmc_writel(ap, p->callback ? PWMC_IER : PWMC_IDR, 1 << p->chan);
+ pr_debug("%s:%d set_callback %p\n", p->pwm->bus_id, p->chan, callback);
+}
+
+
+static int
+atmel_pwm_set_callback(struct pwm_channel *p,
+ pwm_callback_t callback)
+{
+ struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+ unsigned long flags;
+
+ spin_lock_irqsave(&ap->lock, flags);
+ __atmel_pwm_set_callback(p, callback);
+ spin_unlock_irqrestore(&ap->lock, flags);
+
+ return 0;
+}
+
+
+static int
+atmel_pwm_request(struct pwm_channel *p)
+{
+ struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+ unsigned long flags;
+
+ spin_lock_irqsave(&p->lock, flags);
+ clk_enable(ap->clk);
+ p->tick_hz = clk_get_rate(ap->clk);
+ __atmel_pwm_unsynchronize(p, NULL);
+ __atmel_pwm_stop(p);
+ spin_unlock_irqrestore(&p->lock, flags);
+
+ return 0;
+}
+
+
+static void
+atmel_pwm_free(struct pwm_channel *p)
+{
+ struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+ clk_disable(ap->clk);
+}
+
+
+static irqreturn_t
+atmel_pwmc_irq(int irq, void *data)
+{
+ struct atmel_pwm *ap = data;
+ struct pwm_channel *p;
+ u32 isr;
+ int chid;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ap->lock, flags);
+
+ isr = pwmc_readl(ap, PWMC_ISR);
+ for (chid = 0; isr; chid++, isr >>= 1) {
+ p = &ap->pwm.channels[chid];
+ if (isr & 1) {
+ if (p->callback)
+ p->callback(p);
+ if (p->flags & BIT(FLAG_STOP)) {
+ __atmel_pwm_stop(p);
+ clear_bit(FLAG_STOP, &p->flags);
+ }
+ complete_all(&p->complete);
+ }
+ }
+
+ spin_unlock_irqrestore(&ap->lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+
+static int __init
+atmel_pwmc_probe(struct platform_device *pdev)
+{
+ struct atmel_pwm *ap;
+ struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ int ret = 0;
+
+ ap = kzalloc(sizeof(*ap), GFP_KERNEL);
+ if (!ap) {
+ ret = -ENOMEM;
+ goto err_atmel_pwm_alloc;
+ }
+
+ spin_lock_init(&ap->lock);
+ platform_set_drvdata(pdev, ap);
+
+ ap->pwm.bus_id = dev_name(&pdev->dev);
+
+ ap->pwm.nchan = 4; /* TODO: true only for SAM9263 and AP7000 */
+ ap->ccnt_mask = 0xffffUL; /* TODO: true only for SAM9263 */
+
+ ap->sync_mask = kzalloc(ap->pwm.nchan * sizeof(u32), GFP_KERNEL);
+ if (!ap->sync_mask) {
+ ret = -ENOMEM;
+ goto err_alloc_sync_masks;
+ }
+
+ ap->pwm.owner = THIS_MODULE;
+ ap->pwm.request = atmel_pwm_request;
+ ap->pwm.free = atmel_pwm_free;
+ ap->pwm.config_nosleep = atmel_pwm_config_nosleep;
+ ap->pwm.config = atmel_pwm_config;
+ ap->pwm.synchronize = atmel_pwm_synchronize;
+ ap->pwm.unsynchronize = atmel_pwm_unsynchronize;
+ ap->pwm.set_callback = atmel_pwm_set_callback;
+
+ ap->clk = clk_get(&pdev->dev, "pwm_clk");
+ if (IS_ERR(ap->clk)) {
+ pr_info("%s: clk_get error %ld\n",
+ ap->pwm.bus_id, PTR_ERR(ap->clk));
+ ret = -ENODEV;
+ goto err_clk_get;
+ }
+
+ ap->iobase = ioremap_nocache(r->start, r->end - r->start + 1);
+ if (!ap->iobase) {
+ ret = -ENODEV;
+ goto err_ioremap;
+ }
+
+ clk_enable(ap->clk);
+ pwmc_writel(ap, PWMC_DIS, -1);
+ pwmc_writel(ap, PWMC_IDR, -1);
+ clk_disable(ap->clk);
+
+ ap->irq = platform_get_irq(pdev, 0);
+ if (ap->irq != -ENXIO) {
+ ret = request_irq(ap->irq, atmel_pwmc_irq, 0,
+ ap->pwm.bus_id, ap);
+ if (ret)
+ goto err_request_irq;
+ }
+
+ ret = pwm_register(&ap->pwm);
+ if (ret)
+ goto err_pwm_register;
+
+ return 0;
+
+err_pwm_register:
+ if (ap->irq != -ENXIO)
+ free_irq(ap->irq, ap);
+err_request_irq:
+ iounmap(ap->iobase);
+err_ioremap:
+ clk_put(ap->clk);
+err_clk_get:
+ platform_set_drvdata(pdev, NULL);
+err_alloc_sync_masks:
+ kfree(ap);
+err_atmel_pwm_alloc:
+ return ret;
+}
+
+
+static int __devexit
+atmel_pwmc_remove(struct platform_device *pdev)
+{
+ struct atmel_pwm *ap = platform_get_drvdata(pdev);
+ int ret;
+
+ /* TODO: what can we do if this fails? */
+ ret = pwm_unregister(&ap->pwm);
+
+ clk_enable(ap->clk);
+ pwmc_writel(ap, PWMC_IDR, -1);
+ pwmc_writel(ap, PWMC_DIS, -1);
+ clk_disable(ap->clk);
+
+ if (ap->irq != -ENXIO)
+ free_irq(ap->irq, ap);
+
+ clk_put(ap->clk);
+ iounmap(ap->iobase);
+
+ kfree(ap);
+
+ return 0;
+}
+
+
+static struct platform_driver atmel_pwm_driver = {
+ .driver = {
+ .name = "atmel_pwmc",
+ .owner = THIS_MODULE,
+ },
+ .probe = atmel_pwmc_probe,
+ .remove = __devexit_p(atmel_pwmc_remove),
+};
+
+
+static int __init atmel_pwm_init(void)
+{
+ return platform_driver_register(&atmel_pwm_driver);
+}
+module_init(atmel_pwm_init);
+
+
+static void atmel_pwm_exit(void)
+{
+ platform_driver_unregister(&atmel_pwm_driver);
+}
+module_exit(atmel_pwm_exit);
+
+
+MODULE_AUTHOR("Bill Gatliff <bgat@billgatliff.com>");
+MODULE_DESCRIPTION("Driver for Atmel PWMC peripheral");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:atmel_pwmc");
--
1.6.3.3
^ permalink raw reply related
* [[RFC] 2/5] Emulates PWM hardware using a high-resolution timer and a GPIO pin
From: Bill Gatliff @ 2009-10-19 20:32 UTC (permalink / raw)
To: linux-embedded; +Cc: Bill Gatliff
In-Reply-To: <1255984366-26952-2-git-send-email-bgat@billgatliff.com>
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
drivers/pwm/gpio.c | 318 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 318 insertions(+), 0 deletions(-)
create mode 100644 drivers/pwm/gpio.c
diff --git a/drivers/pwm/gpio.c b/drivers/pwm/gpio.c
new file mode 100644
index 0000000..35e5969
--- /dev/null
+++ b/drivers/pwm/gpio.c
@@ -0,0 +1,318 @@
+#define DEBUG 99
+/*
+ * drivers/pwm/gpio.c
+ *
+ * Models a single-channel PWM device using a kernel interval timer
+ * and a GPIO pin.
+ *
+ * Copyright (C) 2008 Bill Gatliff. All Rights Reserved.
+ *
+ * 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/init.h>
+#include <linux/hrtimer.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <mach/gpio.h>
+#include <linux/pwm.h>
+
+
+struct gpio_pwm {
+ struct pwm_device pwm;
+ struct hrtimer t;
+ struct work_struct work;
+ pwm_callback_t callback;
+ int gpio;
+ unsigned long polarity : 1;
+ unsigned long active : 1;
+};
+
+
+static void
+gpio_pwm_work (struct work_struct *work)
+{
+ struct gpio_pwm *gp = container_of(work, struct gpio_pwm, work);
+
+ if (gp->active)
+ gpio_direction_output(gp->gpio, gp->polarity ? 1 : 0);
+ else
+ gpio_direction_output(gp->gpio, gp->polarity ? 0 : 1);
+}
+
+
+static enum hrtimer_restart
+gpio_pwm_timeout(struct hrtimer *t)
+{
+ struct gpio_pwm *gp = container_of(t, struct gpio_pwm, t);
+
+ if (unlikely(gp->pwm.channels[0].duty_ticks == 0))
+ gp->active = 0;
+ else if (unlikely(gp->pwm.channels[0].duty_ticks
+ == gp->pwm.channels[0].period_ticks))
+ gp->active = 1;
+ else
+ gp->active ^= 1;
+
+ if (gpio_cansleep(gp->gpio))
+ schedule_work(&gp->work);
+ else
+ gpio_pwm_work(&gp->work);
+
+ if (!gp->active && gp->pwm.channels[0].callback)
+ gp->pwm.channels[0].callback(&gp->pwm.channels[0]);
+
+ if (unlikely(!gp->active &&
+ (gp->pwm.channels[0].flags & BIT(FLAG_STOP)))) {
+ clear_bit(FLAG_STOP, &gp->pwm.channels[0].flags);
+ complete_all(&gp->pwm.channels[0].complete);
+ return HRTIMER_NORESTART;
+ }
+
+ if (gp->active)
+ hrtimer_start(&gp->t,
+ ktime_set(0, gp->pwm.channels[0].duty_ticks),
+ HRTIMER_MODE_REL);
+ else
+ hrtimer_start(&gp->t,
+ ktime_set(0,gp->pwm.channels[0].period_ticks
+ - gp->pwm.channels[0].duty_ticks),
+ HRTIMER_MODE_REL);
+ return HRTIMER_NORESTART;
+}
+
+
+static void gpio_pwm_start(struct pwm_channel *p)
+{
+ struct gpio_pwm *gp = container_of(p->pwm, struct gpio_pwm, pwm);
+
+ gp->active = 0;
+ gpio_pwm_timeout(&gp->t);
+ pr_debug("%s:%d start, %lu ticks\n",
+ dev_name(p->pwm->dev), p->chan, p->duty_ticks);
+}
+
+
+static int
+gpio_pwm_config_nosleep(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ struct gpio_pwm *gp = container_of(p->pwm, struct gpio_pwm, pwm);
+ int ret = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&p->lock, flags);
+
+ switch (c->config_mask) {
+
+ case PWM_CONFIG_DUTY_TICKS:
+ p->duty_ticks = c->duty_ticks;
+ dev_dbg(p->pwm->dev, ":%d duty_ticks %lu\n",
+ p->chan, p->duty_ticks);
+ break;
+
+ case PWM_CONFIG_START:
+ if (!hrtimer_active(&gp->t)) {
+ gpio_pwm_start(p);
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ spin_unlock_irqrestore(&p->lock, flags);
+ return ret;
+}
+
+
+static int
+gpio_pwm_stop_sync(struct pwm_channel *p)
+{
+ struct gpio_pwm *gp = container_of(p->pwm, struct gpio_pwm, pwm);
+ int ret;
+ int was_on = hrtimer_active(&gp->t);
+
+ if (was_on) {
+ do {
+ init_completion(&p->complete);
+ set_bit(FLAG_STOP, &p->flags);
+
+ dev_dbg(p->pwm->dev, ":%d waiting on stop_sync completion...\n",
+ p->chan);
+
+ ret = wait_for_completion_interruptible(&p->complete);
+
+ dev_dbg(p->pwm->dev, ":%d stop_sync complete (%d)\n",
+ p->chan, ret);
+
+ if (ret)
+ return ret;
+ } while (p->flags & BIT(FLAG_STOP));
+ }
+
+ dev_dbg(p->pwm->dev, ":%d stop_sync returning %d\n",
+ p->chan, was_on);
+
+ return was_on;
+}
+
+
+static int
+gpio_pwm_config(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ struct gpio_pwm *gp = container_of(p->pwm, struct gpio_pwm, pwm);
+ int was_on = 0;
+
+ if (p->pwm->config_nosleep) {
+ if (!p->pwm->config_nosleep(p, c))
+ return 0;
+ }
+
+ might_sleep();
+ dev_dbg(p->pwm->dev, ":%d config_mask %x\n",
+ p->chan, c->config_mask);
+
+ was_on = gpio_pwm_stop_sync(p);
+ if (was_on < 0)
+ return was_on;
+
+ if (c->config_mask & PWM_CONFIG_PERIOD_TICKS)
+ p->period_ticks = c->period_ticks;
+
+ if (c->config_mask & PWM_CONFIG_DUTY_TICKS)
+ p->duty_ticks = c->duty_ticks;
+
+ if (c->config_mask & PWM_CONFIG_POLARITY)
+ gp->polarity = c->polarity ? 1 : 0;
+
+ if ((c->config_mask & PWM_CONFIG_START)
+ || (was_on && !(c->config_mask & PWM_CONFIG_STOP)))
+ gpio_pwm_start(p);
+
+ return 0;
+}
+
+
+static int
+gpio_pwm_set_callback(struct pwm_channel *p,
+ pwm_callback_t callback)
+{
+ struct gpio_pwm *gp = container_of(p->pwm, struct gpio_pwm, pwm);
+ gp->callback = callback;
+ return 0;
+}
+
+
+static int
+gpio_pwm_request(struct pwm_channel *p)
+{
+ p->tick_hz = 1000000000UL;
+ return 0;
+}
+
+
+static int __init
+gpio_pwm_probe(struct platform_device *pdev)
+{
+ struct gpio_pwm *gp;
+ struct gpio_pwm_platform_data *gpd = pdev->dev.platform_data;
+ int ret = 0;
+
+ if (!gpd || gpio_request(gpd->gpio, dev_name(&pdev->dev)))
+ return -EINVAL;
+
+ gp = kzalloc(sizeof(*gp), GFP_KERNEL);
+ if (!gp) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ platform_set_drvdata(pdev, gp);
+
+ gp->pwm.bus_id = dev_name(&pdev->dev);
+ gp->pwm.nchan = 1;
+ gp->gpio = gpd->gpio;
+
+ INIT_WORK(&gp->work, gpio_pwm_work);
+
+ hrtimer_init(&gp->t, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ gp->t.function = gpio_pwm_timeout;
+
+ gp->pwm.owner = THIS_MODULE;
+ gp->pwm.config_nosleep = gpio_pwm_config_nosleep;
+ gp->pwm.config = gpio_pwm_config;
+ gp->pwm.request = gpio_pwm_request;
+ gp->pwm.set_callback = gpio_pwm_set_callback;
+
+ ret = pwm_register(&gp->pwm);
+ if (ret)
+ goto err_pwm_register;
+
+ return 0;
+
+err_pwm_register:
+ kfree(gp);
+err_alloc:
+ return ret;
+}
+
+
+static int __devexit
+gpio_pwm_remove(struct platform_device *pdev)
+{
+ struct gpio_pwm *gp = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pwm_unregister(&gp->pwm);
+ hrtimer_cancel(&gp->t);
+ cancel_work_sync(&gp->work);
+ kfree(gp);
+
+ return 0;
+}
+
+
+static struct platform_driver gpio_pwm_driver = {
+ .driver = {
+ .name = "gpio_pwm",
+ .owner = THIS_MODULE,
+ },
+ .probe = gpio_pwm_probe,
+ .remove = __devexit_p(gpio_pwm_remove),
+};
+
+
+static int __init gpio_pwm_init(void)
+{
+ return platform_driver_register(&gpio_pwm_driver);
+}
+module_init(gpio_pwm_init);
+
+
+static void gpio_pwm_exit(void)
+{
+ platform_driver_unregister(&gpio_pwm_driver);
+}
+module_exit(gpio_pwm_exit);
+
+
+MODULE_AUTHOR("Bill Gatliff <bgat@billgatliff.com>");
+MODULE_DESCRIPTION("PWM output using GPIO and an itimer");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:gpio_pwm");
--
1.6.3.3
^ permalink raw reply related
* [[RFC] 1/5] API to consolidate PWM devices behind a common user and kernel interface
From: Bill Gatliff @ 2009-10-19 20:32 UTC (permalink / raw)
To: linux-embedded; +Cc: Bill Gatliff
In-Reply-To: <1255984366-26952-1-git-send-email-bgat@billgatliff.com>
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
Documentation/pwm.txt | 258 ++++++++++++++++++
drivers/pwm/pwm.c | 692 +++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/pwm.h | 179 +++++++++++--
3 files changed, 1109 insertions(+), 20 deletions(-)
create mode 100644 Documentation/pwm.txt
create mode 100644 drivers/pwm/pwm.c
diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt
new file mode 100644
index 0000000..b8932dd
--- /dev/null
+++ b/Documentation/pwm.txt
@@ -0,0 +1,258 @@
+ Generic PWM Device API
+
+ October 8, 2008
+ Bill Gatliff
+ <bgat@billgatliff.com>
+
+
+
+The code in drivers/pwm and include/linux/pwm.h implements an API for
+applications involving pulse-width-modulation signals. This document
+describes how the API implementation facilitates both PWM-generating
+devices, and users of those devices.
+
+
+
+Motivation
+
+The primary goals for implementing the "generic PWM API" are to
+consolidate the various PWM implementations within a consistent and
+redundancy-reducing framework, and to facilitate the use of
+hotpluggable PWM devices.
+
+Previous PWM-related implementations within the Linux kernel achieved
+their consistency via cut-and-paste, but did not need to (and didn't)
+facilitate more than one PWM-generating device within the system---
+hotplug or otherwise. The Generic PWM Device API might be most
+appropriately viewed as an update to those implementations, rather
+than a complete rewrite.
+
+
+
+Challenges
+
+One of the difficulties in implementing a generic PWM framework is the
+fact that pulse-width-modulation applications involve real-world
+signals, which often must be carefully managed to prevent destruction
+of hardware that is linked to those signals. A DC motor that
+experiences a brief interruption in the PWM signal controlling it
+might destructively overheat; it could change speed, losing
+synchronization with a sensor; it could even suddenly change direction
+or torque, breaking the mechanical device connected to it.
+
+(A generic PWM device framework is not directly responsible for
+preventing the above scenarios: that responsibility lies with the
+hardware designer and the application and driver authors. But it must
+to the greatest extent possible make it easy to avoid such problems).
+
+A generic PWM device framework must accomodate the substantial
+differences between available PWM-generating hardware devices, without
+becoming sub-optimal for any of them.
+
+Finally, a generic PWM device framework must be relatively
+lightweight, computationally speaking. Some PWM users demand
+high-speed outputs, plus the ability to regulate those outputs
+quickly. A device framework must be able to "keep up" with such
+hardware, while still leaving time to do real work.
+
+The Generic PWM Device API is an attempt to meet all of the above
+requirements. At its initial publication, the API was already in use
+managing small DC motors through a custom-designed, optically-isolated
+H-bridge driver.
+
+
+
+Functional Overview
+
+The Generic PWM Device API framework is implemented in
+include/linux/pwm.h and drivers/pwm/pwm.c. The functions therein use
+information from pwm_device, pwm_channel and pwm_channel_config
+structures to invoke services in PWM peripheral device drivers.
+Consult drivers/pwm/atmel-pwm.c for an example driver.
+
+There are two classes of adopters of the PWM framework:
+
+ "Users" -- those wishing to employ the API merely to produce PWM
+ signals; once they have identified the appropriate physical output
+ on the platform in question, they don't care about the details of
+ the underlying hardware
+
+ "Driver authors" -- those wishing to bind devices that can generate
+ PWM signals to the Generic PWM Device API, so that the services of
+ those devices become available to users; assuming the hardware can
+ support the needs of a user, driver authors don't care about the
+ details of the user's application
+
+Generally speaking, users will first invoke pwm_request() to obtain a
+handle to a PWM device. They will then pass that handle to functions
+like pwm_duty_ns() and pwm_period_ns() to set the duty cycle and
+period of the PWM signal, respectively. They will also invoke
+pwm_start() and pwm_stop() to turn the signal on and off.
+
+The framework also provides a sysfs interface to PWM devices, which is
+adequate for basic needs and testing.
+
+Driver authors fill out a pwm_device structure, which describes the
+capabilities of the PWM hardware being driven--- including the number
+of distinct output "channels" the peripheral offers. They then invoke
+pwm_register() (usually from within their device's probe() handler) to
+make the PWM API aware of their device. The framework will call back
+to the methods described in the pwm_device structure to configure and
+use the hardware.
+
+Note that PWM signals can be produced by a variety of peripherals,
+beyond the true "PWM hardware" offered by many system-on-chip devices.
+Other possibilities include timer/counters with compare-match
+capabilities, carefully-programmed synchronous serial ports
+(e.g. SPI), and GPIO pins driven by kernel interval timers. With a
+proper pwm_device structure, these devices and pseudo-devices can all
+be accomodated by the Generic PWM Device API framework.
+
+
+
+Using the API to Generate PWM Signals -- Basic Functions for Users
+
+
+pwm_request() -- Returns a pwm_channel pointer, which is subsequently
+passed to the other user-related PWM functions. Once requested, a PWM
+channel is marked as in-use and subsequent requests prior to
+pwm_free() will fail.
+
+The names used to refer to PWM devices are defined by driver authors.
+Typically they are platform device bus identifiers, and this
+convention is encouraged for consistency.
+
+
+pwm_free() -- Marks a PWM channel as no longer in use. The PWM device
+is stopped before it is released by the API.
+
+
+pwm_period_ns() -- Specifies the PWM signal's period, in nanoseconds.
+
+
+pwm_duty_ns() -- Specifies the PWM signal's active duration, in nanoseconds.
+
+
+pwm_duty_percent() -- Specifies the PWM signal's active duration, as a
+percentage of the current period of the signal. NOTE: this value is
+not recalculated if the period of the signal is subsequently changed.
+
+
+pwm_start(), pwm_stop() -- Turns the PWM signal on and off. Except
+where stated otherwise by a driver author, signals are stopped at the
+end of the current period, at which time the output is set to its
+inactive state.
+
+
+pwm_polarity() -- Defines whether the PWM signal output's active
+region is "1" or "0". A 10% duty-cycle, polarity=1 signal will
+conventionally be at 5V (or 3.3V, or 1000V, or whatever the platform
+hardware does) for 10% of the period. The same configuration of a
+polarity=0 signal will be at 5V (or 3.3V, or ...) for 90% of the
+period.
+
+
+
+Using the API to Generate PWM Signals -- Advanced Functions
+
+
+pwm_config() -- Passes a pwm_channel_config structure to the
+associated device driver. This function is invoked by pwm_start(),
+pwm_duty_ns(), etc. and is one of two main entry points to the PWM
+driver for the hardware being used. The configuration change is
+guaranteed atomic if multiple configuration changes are specified.
+This function might sleep, depending on what the device driver has to
+do to satisfy the request. All PWM device drivers must support this
+entry point.
+
+
+pwm_config_nosleep() -- Passes a pwm_channel_config structure to the
+associated device driver. If the driver must sleep in order to
+implement the requested configuration change, -EWOULDBLOCK is
+returned. Users may call this function from interrupt handlers, for
+example. This is the other main entry point into the PWM hardware
+driver, but not all device drivers support this entry point.
+
+
+pwm_synchronize(), pwm_unsynchronize() -- "Synchronizes" two or more
+PWM channels, if the underlying hardware permits. (If it doesn't, the
+framework facilitates emulating this capability but it is not yet
+implemented). Synchronized channels will start and stop
+simultaneously when any single channel in the group is started or
+stopped. Use pwm_unsynchronize(..., NULL) to completely detach a
+channel from any other synchronized channels.
+
+
+pwm_set_handler() -- Defines an end-of-period callback. The indicated
+function will be invoked in a worker thread at the end of each PWM
+period, and can subsequently invoke pwm_config(), etc. Must be used
+with extreme care for high-speed PWM outputs. Set the handler
+function to NULL to un-set the handler.
+
+
+
+Implementing a PWM Device API Driver -- Functions for Driver Authors
+
+
+Fill out the appropriate fields in a pwm_device structure, and submit
+to pwm_register():
+
+
+bus_id -- the plaintext name of the device. Users will bind to a
+channel on the device using this name plus the channel number. For
+example, the Atmel PWMC's bus_id is "atmel_pwmc", the same as used by
+the platform device driver (recommended). The first device registered
+thereby receives bus_id "atmel_pwmc.0", which is what you put in
+pwm_device.bus_id. Channels are then named "atmel_pwmc.0:[0-3]".
+(Hint: just use pdev->dev.bus_id in your probe() method).
+
+
+nchan -- the number of distinct output channels provided by the device.
+
+
+request -- (optional) Invoked each time a user requests a channel.
+Use to turn on clocks, clean up register states, etc. The framework
+takes care of device locking/unlocking; you will see only successful
+requests.
+
+
+free -- (optional) Callback for each time a user relinquishes a
+channel. The framework will have already stopped, unsynchronized and
+un-handled the channel. Use to turn off clocks, etc. as necessary.
+
+
+synchronize, unsynchronize -- (optional) Callbacks to
+synchronize/unsynchronize channels. Some devices provide this
+capability in hardware; for others, it can be emulated (see
+atmel_pwmc.c's sync_mask for an example).
+
+
+set_callback -- (optional) Invoked when a user requests a handler. If
+the hardware supports an end-of-period interrupt, invoke the function
+indicated during your interrupt handler. The callback function itself
+is always internal to the API, and does not map directly to the user's
+callback function.
+
+
+config -- Invoked to change the device configuration, always from a
+sleep-capable context. All the changes indicated must be performed
+atomically, ideally synchronized to an end-of-period event (so that
+you avoid short or long output pulses). You may sleep, etc. as
+necessary within this function.
+
+
+config_nosleep -- (optional) Invoked to change device configuration
+from within a context that is not allowed to sleep. If you cannot
+perform the requested configuration changes without sleeping, return
+-EWOULDBLOCK.
+
+
+
+Acknowledgements
+
+
+The author expresses his gratitude to the countless developers who
+have reviewed and submitted feedback on the various versions of the
+Generic PWM Device API code, and those who have submitted drivers and
+applications that use the framework. You know who you are. ;)
+
diff --git a/drivers/pwm/pwm.c b/drivers/pwm/pwm.c
new file mode 100644
index 0000000..2083fde
--- /dev/null
+++ b/drivers/pwm/pwm.c
@@ -0,0 +1,692 @@
+#define DEBUG 99
+
+/*
+ * drivers/pwm/pwm.c
+ *
+ * Copyright (C) 2008 Bill Gatliff
+ *
+ * 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.
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <linux/fs.h>
+#include <linux/completion.h>
+#include <linux/workqueue.h>
+#include <linux/pwm.h>
+
+
+static int __pwm_create_sysfs(struct pwm_device *pwm);
+
+static LIST_HEAD(pwm_device_list);
+static DEFINE_MUTEX(device_list_mutex);
+static struct class pwm_class;
+static struct workqueue_struct *pwm_handler_workqueue;
+
+
+int pwm_register(struct pwm_device *pwm)
+{
+ struct pwm_channel *p;
+ int wchan;
+ int ret;
+
+ spin_lock_init(&pwm->list_lock);
+
+ p = kcalloc(pwm->nchan, sizeof(struct pwm_channel), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ for (wchan = 0; wchan < pwm->nchan; wchan++) {
+ spin_lock_init(&p[wchan].lock);
+ init_completion(&p[wchan].complete);
+ p[wchan].chan = wchan;
+ p[wchan].pwm = pwm;
+ }
+
+ pwm->channels = p;
+
+ mutex_lock(&device_list_mutex);
+
+ list_add_tail(&pwm->list, &pwm_device_list);
+ ret = __pwm_create_sysfs(pwm);
+ if (ret) {
+ mutex_unlock(&device_list_mutex);
+ goto err_create_sysfs;
+ }
+
+ mutex_unlock(&device_list_mutex);
+
+ pr_info("%s: %d channel%s\n", pwm->bus_id, pwm->nchan,
+ pwm->nchan > 1 ? "s" : "");
+ return 0;
+
+err_create_sysfs:
+ kfree(p);
+
+ return ret;
+}
+EXPORT_SYMBOL(pwm_register);
+
+
+static int __match_device(struct device *dev, void *data)
+{
+ return dev_get_drvdata(dev) == data;
+}
+
+
+int pwm_unregister(struct pwm_device *pwm)
+{
+ int wchan;
+ struct device *dev;
+
+ mutex_lock(&device_list_mutex);
+
+ for (wchan = 0; wchan < pwm->nchan; wchan++) {
+ if (pwm->channels[wchan].flags & FLAG_REQUESTED) {
+ mutex_unlock(&device_list_mutex);
+ return -EBUSY;
+ }
+ }
+
+ for (wchan = 0; wchan < pwm->nchan; wchan++) {
+ dev = class_find_device(&pwm_class, NULL,
+ &pwm->channels[wchan],
+ __match_device);
+ if (dev) {
+ put_device(dev);
+ device_unregister(dev);
+ }
+ }
+
+ kfree(pwm->channels);
+ list_del(&pwm->list);
+ mutex_unlock(&device_list_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL(pwm_unregister);
+
+
+static struct pwm_device *
+__pwm_find_device(const char *bus_id)
+{
+ struct pwm_device *p;
+
+ list_for_each_entry(p, &pwm_device_list, list)
+ {
+ if (!strcmp(bus_id, p->bus_id))
+ return p;
+ }
+ return NULL;
+}
+
+
+static int
+__pwm_request_channel(struct pwm_channel *p,
+ const char *requester)
+{
+ int ret;
+
+ if (test_and_set_bit(FLAG_REQUESTED, &p->flags))
+ return -EBUSY;
+
+ if (p->pwm->request) {
+ ret = p->pwm->request(p);
+ if (ret) {
+ clear_bit(FLAG_REQUESTED, &p->flags);
+ return ret;
+ }
+ }
+
+ p->requester = requester;
+ return 0;
+}
+
+
+struct pwm_channel *
+pwm_request(const char *bus_id,
+ int chan,
+ const char *requester)
+{
+ struct pwm_device *p;
+ int ret;
+
+ mutex_lock(&device_list_mutex);
+
+ p = __pwm_find_device(bus_id);
+ if (!p || chan >= p->nchan)
+ goto err_no_device;
+
+ if (!try_module_get(p->owner))
+ goto err_module_get_failed;
+
+ ret = __pwm_request_channel(&p->channels[chan], requester);
+ if (ret)
+ goto err_request_failed;
+
+ mutex_unlock(&device_list_mutex);
+
+ pr_debug("%s: %s:%d returns %p\n", __func__,
+ bus_id, chan, &p->channels[chan]);
+
+ return &p->channels[chan];
+
+err_request_failed:
+ module_put(p->owner);
+err_module_get_failed:
+err_no_device:
+
+ mutex_unlock(&device_list_mutex);
+
+ pr_debug("%s: %s:%d returns NULL\n",
+ __func__, bus_id, chan);
+
+ return NULL;
+}
+EXPORT_SYMBOL(pwm_request);
+
+
+void pwm_free(struct pwm_channel *p)
+{
+ mutex_lock(&device_list_mutex);
+
+ if (!test_and_clear_bit(FLAG_REQUESTED, &p->flags))
+ goto done;
+
+ pwm_stop(p);
+ pwm_unsynchronize(p, NULL);
+ pwm_set_handler(p, NULL, NULL);
+
+ if (p->pwm->free)
+ p->pwm->free(p);
+ module_put(p->pwm->owner);
+
+ pr_debug("%s: %s:%d free\n",
+ __func__, p->pwm->bus_id, p->chan);
+
+done:
+ mutex_unlock(&device_list_mutex);
+}
+EXPORT_SYMBOL(pwm_free);
+
+
+unsigned long pwm_ns_to_ticks(struct pwm_channel *p,
+ unsigned long nsecs)
+{
+ unsigned long long ticks;
+
+ ticks = nsecs;
+ ticks *= p->tick_hz;
+ do_div(ticks, 1000000000);
+ return ticks;
+}
+EXPORT_SYMBOL(pwm_ns_to_ticks);
+
+
+unsigned long pwm_ticks_to_ns(struct pwm_channel *p,
+ unsigned long ticks)
+{
+ unsigned long long ns;
+
+ if (!p->tick_hz)
+ return 0;
+
+ ns = ticks;
+ ns *= 1000000000UL;
+ do_div(ns, p->tick_hz);
+ return ns;
+}
+EXPORT_SYMBOL(pwm_ticks_to_ns);
+
+
+static void
+pwm_config_ns_to_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ if (c->config_mask & PWM_CONFIG_PERIOD_NS) {
+ c->period_ticks = pwm_ns_to_ticks(p, c->period_ns);
+ c->config_mask &= ~PWM_CONFIG_PERIOD_NS;
+ c->config_mask |= PWM_CONFIG_PERIOD_TICKS;
+ }
+
+ if (c->config_mask & PWM_CONFIG_DUTY_NS) {
+ c->duty_ticks = pwm_ns_to_ticks(p, c->duty_ns);
+ c->config_mask &= ~PWM_CONFIG_DUTY_NS;
+ c->config_mask |= PWM_CONFIG_DUTY_TICKS;
+ }
+}
+
+
+static void
+pwm_config_percent_to_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ if (c->config_mask & PWM_CONFIG_DUTY_PERCENT) {
+ if (c->config_mask & PWM_CONFIG_PERIOD_TICKS)
+ c->duty_ticks = c->period_ticks;
+ else
+ c->duty_ticks = p->period_ticks;
+
+ c->duty_ticks *= c->duty_percent;
+ c->duty_ticks /= 100;
+ c->config_mask &= ~PWM_CONFIG_DUTY_PERCENT;
+ c->config_mask |= PWM_CONFIG_DUTY_TICKS;
+ }
+}
+
+
+int pwm_config_nosleep(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ if (!p->pwm->config_nosleep)
+ return -EINVAL;
+
+ pwm_config_ns_to_ticks(p, c);
+ pwm_config_percent_to_ticks(p, c);
+
+ return p->pwm->config_nosleep(p, c);
+}
+EXPORT_SYMBOL(pwm_config_nosleep);
+
+
+int pwm_config(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ int ret = 0;
+
+ if (unlikely(!p->pwm->config)) {
+ pr_debug("%s: %s:%d has no config handler (-EINVAL)\n",
+ __func__, p->pwm->bus_id, p->chan);
+ return -EINVAL;
+ }
+
+ pwm_config_ns_to_ticks(p, c);
+ pwm_config_percent_to_ticks(p, c);
+
+ switch (c->config_mask & (PWM_CONFIG_PERIOD_TICKS
+ | PWM_CONFIG_DUTY_TICKS)) {
+ case PWM_CONFIG_PERIOD_TICKS:
+ if (p->duty_ticks > c->period_ticks) {
+ ret = -EINVAL;
+ goto err;
+ }
+ break;
+ case PWM_CONFIG_DUTY_TICKS:
+ if (p->period_ticks < c->duty_ticks) {
+ ret = -EINVAL;
+ goto err;
+ }
+ break;
+ case PWM_CONFIG_DUTY_TICKS | PWM_CONFIG_PERIOD_TICKS:
+ if (c->duty_ticks > c->period_ticks) {
+ ret = -EINVAL;
+ goto err;
+ }
+ break;
+ default:
+ break;
+ }
+
+err:
+ pr_debug("%s: config_mask %d period_ticks %lu duty_ticks %lu"
+ " polarity %d duty_ns %lu period_ns %lu duty_percent %d\n",
+ __func__, c->config_mask, c->period_ticks, c->duty_ticks,
+ c->polarity, c->duty_ns, c->period_ns, c->duty_percent);
+
+ if (ret)
+ return ret;
+ return p->pwm->config(p, c);
+}
+EXPORT_SYMBOL(pwm_config);
+
+
+int pwm_set_period_ns(struct pwm_channel *p,
+ unsigned long period_ns)
+{
+ struct pwm_channel_config c = {
+ .config_mask = PWM_CONFIG_PERIOD_TICKS,
+ .period_ticks = pwm_ns_to_ticks(p, period_ns),
+ };
+
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_set_period_ns);
+
+
+unsigned long pwm_get_period_ns(struct pwm_channel *p)
+{
+ return pwm_ticks_to_ns(p, p->period_ticks);
+}
+EXPORT_SYMBOL(pwm_get_period_ns);
+
+
+int pwm_set_duty_ns(struct pwm_channel *p,
+ unsigned long duty_ns)
+{
+ struct pwm_channel_config c = {
+ .config_mask = PWM_CONFIG_DUTY_TICKS,
+ .duty_ticks = pwm_ns_to_ticks(p, duty_ns),
+ };
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_set_duty_ns);
+
+
+unsigned long pwm_get_duty_ns(struct pwm_channel *p)
+{
+ return pwm_ticks_to_ns(p, p->duty_ticks);
+}
+EXPORT_SYMBOL(pwm_get_duty_ns);
+
+
+int pwm_set_duty_percent(struct pwm_channel *p,
+ int percent)
+{
+ struct pwm_channel_config c = {
+ .config_mask = PWM_CONFIG_DUTY_PERCENT,
+ .duty_percent = percent,
+ };
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_set_duty_percent);
+
+
+int pwm_set_polarity(struct pwm_channel *p,
+ int active_high)
+{
+ struct pwm_channel_config c = {
+ .config_mask = PWM_CONFIG_POLARITY,
+ .polarity = active_high,
+ };
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_set_polarity);
+
+
+int pwm_start(struct pwm_channel *p)
+{
+ struct pwm_channel_config c = {
+ .config_mask = PWM_CONFIG_START,
+ };
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_start);
+
+
+int pwm_stop(struct pwm_channel *p)
+{
+ struct pwm_channel_config c = {
+ .config_mask = PWM_CONFIG_STOP,
+ };
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_stop);
+
+
+int pwm_synchronize(struct pwm_channel *p,
+ struct pwm_channel *to_p)
+{
+ if (p->pwm != to_p->pwm) {
+ /* TODO: support cross-device synchronization */
+ return -EINVAL;
+ }
+
+ if (!p->pwm->synchronize)
+ return -EINVAL;
+
+ return p->pwm->synchronize(p, to_p);
+}
+EXPORT_SYMBOL(pwm_synchronize);
+
+
+int pwm_unsynchronize(struct pwm_channel *p,
+ struct pwm_channel *from_p)
+{
+ if (from_p && (p->pwm != from_p->pwm)) {
+ /* TODO: support cross-device synchronization */
+ return -EINVAL;
+ }
+
+ if (!p->pwm->unsynchronize)
+ return -EINVAL;
+
+ return p->pwm->unsynchronize(p, from_p);
+}
+EXPORT_SYMBOL(pwm_unsynchronize);
+
+
+static void pwm_handler(struct work_struct *w)
+{
+ struct pwm_channel *p = container_of(w, struct pwm_channel,
+ handler_work);
+ if (p->handler && p->handler(p, p->handler_data))
+ pwm_stop(p);
+}
+
+
+static void __pwm_callback(struct pwm_channel *p)
+{
+ queue_work(pwm_handler_workqueue, &p->handler_work);
+ pr_debug("%s:%d handler %p scheduled with data %p\n",
+ p->pwm->bus_id, p->chan, p->handler, p->handler_data);
+}
+
+
+int pwm_set_handler(struct pwm_channel *p,
+ pwm_handler_t handler,
+ void *data)
+{
+ if (p->pwm->set_callback) {
+ p->handler_data = data;
+ p->handler = handler;
+ INIT_WORK(&p->handler_work, pwm_handler);
+ return p->pwm->set_callback(p, handler ? __pwm_callback : NULL);
+ }
+ return -EINVAL;
+}
+EXPORT_SYMBOL(pwm_set_handler);
+
+
+static ssize_t pwm_run_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t len)
+{
+ struct pwm_channel *p = dev_get_drvdata(dev);
+ if (sysfs_streq(buf, "1"))
+ pwm_start(p);
+ else if (sysfs_streq(buf, "0"))
+ pwm_stop(p);
+ return len;
+}
+static DEVICE_ATTR(run, 0200, NULL, pwm_run_store);
+
+
+static ssize_t pwm_duty_ns_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_channel *p = dev_get_drvdata(dev);
+ return sprintf(buf, "%lu\n", pwm_get_duty_ns(p));
+}
+
+static ssize_t pwm_duty_ns_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t len)
+{
+ unsigned long duty_ns;
+ struct pwm_channel *p = dev_get_drvdata(dev);
+
+ if (1 == sscanf(buf, "%lu", &duty_ns))
+ pwm_set_duty_ns(p, duty_ns);
+ return len;
+}
+static DEVICE_ATTR(duty_ns, 0644, pwm_duty_ns_show, pwm_duty_ns_store);
+
+
+static ssize_t pwm_period_ns_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_channel *p = dev_get_drvdata(dev);
+ return sprintf(buf, "%lu\n", pwm_get_period_ns(p));
+}
+
+
+static ssize_t pwm_period_ns_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t len)
+{
+ unsigned long period_ns;
+ struct pwm_channel *p = dev_get_drvdata(dev);
+
+ if (1 == sscanf(buf, "%lu", &period_ns))
+ pwm_set_period_ns(p, period_ns);
+ return len;
+}
+static DEVICE_ATTR(period_ns, 0644, pwm_period_ns_show, pwm_period_ns_store);
+
+
+static ssize_t pwm_polarity_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_channel *p = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n", p->active_low ? 0 : 1);
+}
+
+
+static ssize_t pwm_polarity_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t len)
+{
+ int polarity;
+ struct pwm_channel *p = dev_get_drvdata(dev);
+
+ if (1 == sscanf(buf, "%d", &polarity))
+ pwm_set_polarity(p, polarity);
+ return len;
+}
+static DEVICE_ATTR(polarity, 0644, pwm_polarity_show, pwm_polarity_store);
+
+
+static ssize_t pwm_request_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_channel *p = dev_get_drvdata(dev);
+ mutex_lock(&device_list_mutex);
+ __pwm_request_channel(p, "sysfs");
+ mutex_unlock(&device_list_mutex);
+
+ return sprintf(buf, "%s\n", p->requester);
+}
+
+
+static ssize_t pwm_request_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t len)
+{
+ struct pwm_channel *p = dev_get_drvdata(dev);
+ pwm_free(p);
+ return len;
+}
+static DEVICE_ATTR(request, 0644, pwm_request_show, pwm_request_store);
+
+
+static const struct attribute *pwm_attrs[] =
+{
+ &dev_attr_run.attr,
+ &dev_attr_polarity.attr,
+ &dev_attr_duty_ns.attr,
+ &dev_attr_period_ns.attr,
+ &dev_attr_request.attr,
+ NULL,
+};
+
+
+static const struct attribute_group pwm_device_attr_group = {
+ .attrs = (struct attribute **)pwm_attrs,
+};
+
+
+static int __pwm_create_sysfs(struct pwm_device *pwm)
+{
+ int ret = 0;
+ struct device *dev;
+ int wchan;
+
+ for (wchan = 0; wchan < pwm->nchan; wchan++) {
+ dev = device_create(&pwm_class, pwm->dev, MKDEV(0, 0),
+ pwm->channels + wchan,
+ "%s:%d", pwm->bus_id, wchan);
+ if (!dev)
+ goto err_dev_create;
+ ret = sysfs_create_group(&dev->kobj, &pwm_device_attr_group);
+ if (ret)
+ goto err_dev_create;
+ }
+
+ return ret;
+
+err_dev_create:
+ for (wchan = 0; wchan < pwm->nchan; wchan++) {
+ dev = class_find_device(&pwm_class, NULL,
+ &pwm->channels[wchan],
+ __match_device);
+ if (dev) {
+ put_device(dev);
+ device_unregister(dev);
+ }
+ }
+
+ return ret;
+}
+
+
+static struct class_attribute pwm_class_attrs[] = {
+ __ATTR_NULL,
+};
+
+static struct class pwm_class = {
+ .name = "pwm",
+ .owner = THIS_MODULE,
+
+ .class_attrs = pwm_class_attrs,
+};
+
+
+static int __init pwm_init(void)
+{
+ int ret;
+
+ /* TODO: how to deal with devices that register very early? */
+
+ ret = class_register(&pwm_class);
+ if (ret < 0)
+ return ret;
+
+ pwm_handler_workqueue = create_workqueue("pwmd");
+
+ return 0;
+}
+postcore_initcall(pwm_init);
diff --git a/include/linux/pwm.h b/include/linux/pwm.h
index 7c77575..848cd76 100644
--- a/include/linux/pwm.h
+++ b/include/linux/pwm.h
@@ -1,31 +1,170 @@
#ifndef __LINUX_PWM_H
#define __LINUX_PWM_H
-struct pwm_device;
-
/*
- * pwm_request - request a PWM device
+ * include/linux/pwm.h
+ *
+ * Copyright (C) 2008 Bill Gatliff
+ *
+ * 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.
+ *
+ * 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
*/
-struct pwm_device *pwm_request(int pwm_id, const char *label);
-/*
- * pwm_free - free a PWM device
- */
-void pwm_free(struct pwm_device *pwm);
+#include <linux/completion.h>
+#include <linux/workqueue.h>
+#include <linux/spinlock.h>
+#include <linux/list.h>
-/*
- * pwm_config - change a PWM device configuration
- */
-int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
+enum {
+ PWM_CONFIG_DUTY_TICKS = BIT(0),
+ PWM_CONFIG_PERIOD_TICKS = BIT(1),
+ PWM_CONFIG_POLARITY = BIT(2),
+ PWM_CONFIG_START = BIT(3),
+ PWM_CONFIG_STOP = BIT(4),
-/*
- * pwm_enable - start a PWM output toggling
- */
-int pwm_enable(struct pwm_device *pwm);
+ PWM_CONFIG_HANDLER = BIT(5),
+
+ PWM_CONFIG_DUTY_NS = BIT(6),
+ PWM_CONFIG_DUTY_PERCENT = BIT(7),
+ PWM_CONFIG_PERIOD_NS = BIT(8),
+};
+
+struct pwm_channel;
+struct work_struct;
+
+typedef int (*pwm_handler_t)(struct pwm_channel *p, void *data);
+typedef void (*pwm_callback_t)(struct pwm_channel *p);
+
+struct pwm_channel_config {
+ int config_mask;
+ unsigned long duty_ticks;
+ unsigned long period_ticks;
+ int polarity;
+
+ pwm_handler_t handler;
+
+ unsigned long duty_ns;
+ unsigned long period_ns;
+ int duty_percent;
+};
+
+struct pwm_device {
+ struct list_head list;
+ spinlock_t list_lock;
+ struct device *dev;
+ struct module *owner;
+ struct pwm_channel *channels;
+
+ const char *bus_id;
+ int nchan;
+
+ int (*request) (struct pwm_channel *p);
+ void (*free) (struct pwm_channel *p);
+ int (*config) (struct pwm_channel *p,
+ struct pwm_channel_config *c);
+ int (*config_nosleep)(struct pwm_channel *p,
+ struct pwm_channel_config *c);
+ int (*synchronize) (struct pwm_channel *p,
+ struct pwm_channel *to_p);
+ int (*unsynchronize)(struct pwm_channel *p,
+ struct pwm_channel *from_p);
+ int (*set_callback) (struct pwm_channel *p,
+ pwm_callback_t callback);
+};
+
+int pwm_register(struct pwm_device *pwm);
+int pwm_unregister(struct pwm_device *pwm);
+
+enum {
+ FLAG_REQUESTED = 0,
+ FLAG_STOP = 1,
+};
+
+struct pwm_channel {
+ struct list_head list;
+ struct pwm_device *pwm;
+ const char *requester;
+ int chan;
+ unsigned long flags;
+ unsigned long tick_hz;
+
+ spinlock_t lock;
+ struct completion complete;
+
+ pwm_callback_t callback;
+
+ struct work_struct handler_work;
+ pwm_handler_t handler;
+ void *handler_data;
+
+ int active_low;
+ unsigned long period_ticks;
+ unsigned long duty_ticks;
+};
+
+struct gpio_pwm_platform_data {
+ int gpio;
+};
+
+struct pwm_channel *
+pwm_request(const char *bus_id, int chan,
+ const char *requester);
+
+void pwm_free(struct pwm_channel *pwm);
+
+int pwm_config_nosleep(struct pwm_channel *pwm,
+ struct pwm_channel_config *c);
+
+int pwm_config(struct pwm_channel *pwm,
+ struct pwm_channel_config *c);
+
+unsigned long pwm_ns_to_ticks(struct pwm_channel *pwm,
+ unsigned long nsecs);
+
+unsigned long pwm_ticks_to_ns(struct pwm_channel *pwm,
+ unsigned long ticks);
+
+int pwm_set_period_ns(struct pwm_channel *pwm,
+ unsigned long period_ns);
+
+unsigned long int pwm_get_period_ns(struct pwm_channel *pwm);
+
+int pwm_set_duty_ns(struct pwm_channel *pwm,
+ unsigned long duty_ns);
+
+int pwm_set_duty_percent(struct pwm_channel *pwm,
+ int percent);
+
+unsigned long pwm_get_duty_ns(struct pwm_channel *pwm);
+
+int pwm_set_polarity(struct pwm_channel *pwm,
+ int active_high);
+
+int pwm_start(struct pwm_channel *pwm);
+
+int pwm_stop(struct pwm_channel *pwm);
+
+int pwm_set_handler(struct pwm_channel *pwm,
+ pwm_handler_t handler,
+ void *data);
+
+int pwm_synchronize(struct pwm_channel *p,
+ struct pwm_channel *to_p);
+
+
+int pwm_unsynchronize(struct pwm_channel *p,
+ struct pwm_channel *from_p);
-/*
- * pwm_disable - stop a PWM output toggling
- */
-void pwm_disable(struct pwm_device *pwm);
#endif /* __LINUX_PWM_H */
--
1.6.3.3
^ permalink raw reply related
* [[RFC] 0/5] Generic PWM API Proposal
From: Bill Gatliff @ 2009-10-19 20:32 UTC (permalink / raw)
To: linux-embedded; +Cc: Bill Gatliff
This patch series extends the existing PWM API into something more
generic, and adds support for hotplugging. A driver for the Atmel
SAM9263 PWMC peripheral is provided, as well as a "leds-pwm" wedge
and an "led-dim" trigger that allow the LED API to take advantage
of this new API. The code has been run-tested on a SAM9263 platform,
the OMAP3430 Beagleboard platform, and a few others.
The motivation for creating this API is the author's need for hotpluggable
PWM devices, including i2c GPIO expander chips. The existing PWM API's
use of integers to enumerate PWM channels was problematic for generic
hotplug situations, so the new API creates pwm_device and pwm_channel
structures. Under the new API, PWM channels are identified by the platform
device's bus_id plus an integer channel selector for that device.
The API does not deal with I/O line multiplexing. It assumes that the
platform code or bootloader have set up the output lines as necessary.
The proposed API targets the most basic capabilities required by PWM signals,
namely the ability to vary the period and duty cycle of the waveform. The
author believes that other, more-sophisticated features like channel
synchronization, frequency slewing, end-of-period interrupts, etc. that
are necessary for applications like motor control can be added without
substantially altering the proposed API. Testing of these features
will in some cases require hardware that the author does not currently have
access to. (Hint, hint).
The author wishes to express his appreciation to Russell King, David Brownell,
Ulf Samuelsson, Eric Maio, Haavard Skinnemoen, and others who helped him
formulate the API and reviewed early releases of the code.
Bill Gatliff (5):
API to consolidate PWM devices behind a common user and kernel
interface
Emulates PWM hardware using a high-resolution timer and a GPIO pin
Expunge old Atmel PWMC driver, replacing it with one that conforms to
the PWM API
An LED "dimmer" trigger, which uses the PWM API to vary the
brightness of an LED according to system load
Incorporate PWM API code into KBuild
Documentation/pwm.txt | 258 ++++++++++++++++
drivers/Kconfig | 2 +
drivers/Makefile | 2 +
drivers/leds/Kconfig | 32 ++-
drivers/leds/Makefile | 3 +
drivers/leds/leds-pwm.c | 224 ++++++++-------
drivers/leds/ledtrig-dim.c | 95 ++++++
drivers/misc/Makefile | 6 +-
drivers/misc/atmel_pwm.c | 409 --------------------------
drivers/pwm/Kconfig | 30 ++
drivers/pwm/Makefile | 7 +
drivers/pwm/atmel-pwm.c | 633 ++++++++++++++++++++++++++++++++++++++++
drivers/pwm/gpio.c | 318 ++++++++++++++++++++
drivers/pwm/pwm.c | 692 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/pwm-led.h | 34 +++
include/linux/pwm.h | 179 ++++++++++--
16 files changed, 2384 insertions(+), 540 deletions(-)
create mode 100644 Documentation/pwm.txt
create mode 100644 drivers/leds/ledtrig-dim.c
delete mode 100644 drivers/misc/atmel_pwm.c
create mode 100644 drivers/pwm/Kconfig
create mode 100644 drivers/pwm/Makefile
create mode 100644 drivers/pwm/atmel-pwm.c
create mode 100644 drivers/pwm/gpio.c
create mode 100644 drivers/pwm/pwm.c
create mode 100644 include/linux/pwm-led.h
^ permalink raw reply
* Re: [RFC] misc/at24: add experimental OF support for the generic eeprom driver
From: Wolfram Sang @ 2009-10-09 16:20 UTC (permalink / raw)
To: Nate Case; +Cc: devicetree-discuss, linux-embedded, linuxppc-dev, linux-i2c
In-Reply-To: <1255096871.16018.49.camel@localhost.localdomain>
[-- Attachment #1.1: Type: text/plain, Size: 873 bytes --]
> I can submit that soon, but it probably makes sense for Wolfram to
> voice whatever his concerns were about "questionable" properties before
> I document what's there.
Please don't feel offended. The things I noticed are:
a) no documentation
b) 'polarity' is a direct mapping to the register which IMO is a hint to look
closer. I haven't checked in detil, but maybe the active_low-flag could be used
for this?
I mainly got alarmed that properties were mainlined without being reviewed; as
the device-tree is based on convention (which is hard to change afterwards), I
try to make sure this will not so easily happen again (thus the
get_maintainer-patch on lkml).
Regards,
Wolfram
--
Pengutronix e.K. | Wolfram Sang |
Industrial Linux Solutions | http://www.pengutronix.de/ |
[-- Attachment #1.2: Digital signature --]
[-- Type: application/pgp-signature, Size: 197 bytes --]
[-- Attachment #2: Type: text/plain, Size: 150 bytes --]
_______________________________________________
Linuxppc-dev mailing list
Linuxppc-dev@lists.ozlabs.org
https://lists.ozlabs.org/listinfo/linuxppc-dev
^ permalink raw reply
* Re: [RFC] misc/at24: add experimental OF support for the generic eeprom driver
From: Grant Likely @ 2009-10-09 16:13 UTC (permalink / raw)
To: Nate Case; +Cc: devicetree-discuss, linux-embedded, linuxppc-dev, linux-i2c
In-Reply-To: <1255095793.16018.32.camel@localhost.localdomain>
On Fri, Oct 9, 2009 at 7:43 AM, Nate Case <ncase@xes-inc.com> wrote:
> On Fri, 2009-10-09 at 07:14 +0200, Wolfram Sang wrote:
>> And while doing this and figuring the pro/cons of those methods, I
>> stumbled over this commit:
>>
>> gpio: pca953x: Get platform_data from OpenFirmware
>> (1965d30356c1c65660ba3330927671cfe81acdd5)
>
> Aside from any issues you have with the properties themselves, what's
> your take on this approach?
As I mentioned in an earlier email, I don't think quite the right form
has been found yet, but I like the direction things are moving.
> Personally, I just got tired of waiting for someone else to solve the
> pdata/OF problem. So I submitted that commit as an attempt at something
> very simple and unobtrusive to the device driver itself. It seems
> pretty clean to me, but I'm curious to see if others have any better
> ideas.
Yup, that's good. Between Anton's, Wolfram's and your work things are
going the right way.
g.
--
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.
^ permalink raw reply
* Re: [RFC] misc/at24: add experimental OF support for the generic eeprom driver
From: Wolfram Sang @ 2009-10-09 16:12 UTC (permalink / raw)
To: Nate Case; +Cc: devicetree-discuss, linux-embedded, linuxppc-dev, linux-i2c
In-Reply-To: <1255095793.16018.32.camel@localhost.localdomain>
[-- Attachment #1.1: Type: text/plain, Size: 595 bytes --]
> Aside from any issues you have with the properties themselves, what's
> your take on this approach?
Well, my approach for AT24 looked very similar to your approach. In fact, even
the motivation was the same as yours :) Well, the outcome of this is the
current thread and no definite solution yet. The archdata surely helps for this
issue, it just seems that a bit more generalization is needed.
Kind regards,
Wolfram
--
Pengutronix e.K. | Wolfram Sang |
Industrial Linux Solutions | http://www.pengutronix.de/ |
[-- Attachment #1.2: Digital signature --]
[-- Type: application/pgp-signature, Size: 197 bytes --]
[-- Attachment #2: Type: text/plain, Size: 150 bytes --]
_______________________________________________
Linuxppc-dev mailing list
Linuxppc-dev@lists.ozlabs.org
https://lists.ozlabs.org/listinfo/linuxppc-dev
^ permalink raw reply
* Re: [RFC] misc/at24: add experimental OF support for the generic eeprom driver
From: Grant Likely @ 2009-10-09 16:09 UTC (permalink / raw)
To: Nate Case; +Cc: devicetree-discuss, linux-embedded, linuxppc-dev, linux-i2c
In-Reply-To: <1255096871.16018.49.camel@localhost.localdomain>
On Fri, Oct 9, 2009 at 8:01 AM, Nate Case <ncase@xes-inc.com> wrote:
> On Thu, 2009-10-08 at 23:40 -0600, Grant Likely wrote:
>> For your future reference, patches that look at the device tree should
>> also cc: devicetree-discuss@lists.ozlabs.org so that new bindings can
>> be reviewed and common mistakes can be avoided. It is expected that
>> new device tree bindings are accompanied with documentation describing
>> what the binding is for and how it should be used (see
>> Documentation/powerpc/dts-bindings).
>>
>> I know this change is already in mainline, but can you please post the
>> device tree fragment that you're using to describe this chip? I want
>> to make sure we don't get stuck with things in the kernel that will be
>> hard to maintain in the long term.
>
> Hi Grant,
>
> Sorry for neglecting to include devicetree-discuss on that one. I was
> in fact aware of this list, and subscribe to it. I really just forgot
> in this case.
No worries, it happens.
> I also have a documentation patch for it that went along with it, but it
> wasn't ready in time and so it's been sitting in our local patch queue.
> I can submit that soon, but it probably makes sense for Wolfram to
> voice whatever his concerns were about "questionable" properties before
> I document what's there.
Yes, please post it as soon as you can.
> Here's an example device tree node for this case:
>
> gpio1: gpio@18 {
> compatible = "nxp,pca9557";
> reg = <0x18>;
> #gpio-cells = <2>;
> gpio-controller;
> polarity = <0x00>;
> };
>
> In this case, the linux,gpio-base property wasn't specified. But, the
> use case is identical to the pdata->gpio_base field. "polarity" is used
> for specifying polarity inversion for each line, and is in the same
> format of the 'polarity inversion' register on the chip. My reasoning
> in the property naming was as follows:
>
> linux,gpio-base: Linux-specific as it relates to internal GPIO
> numbering. So, it's prefixed with "linux,"
This would be the 'questionable' property. :-) The device tree is
supposed to be OS agnostic, so I get the heebee geebees when I see new
'linux,<blah>' properties defined because it means Linux internal
implementation details are getting leaked out into the data blob. The
problem leakage is that the device tree should not be impacted by
internal Linux code changes.
In this particular case, specifying the exact GPIO base number doesn't
really belong in the device tree because Linux is able to assign and
manage GPIO numbers on its own just like all other OF GPIO controller
drivers currently do. In fact, that goes for pretty much all
enumeration, not just GPIO. In general, a particular device instance
(uart, gpio, phy, whatever) should be resolved from its node in the
device tree, and not by some arbitrary number assigned by the .dts
author. The problem with arbitrary numbers is they don't expose the
linkage between nodes in the same way a 'phandle' does (A phandle can
be searched for. An arbitrary number assignment, good luck?), and
they don't expose intended usage (its just a meaningless number, and
it only works because userspace just happens to 'agree' to use the
same number).
> polarity: Dictated by how hardware is wired up, so it's needed
> regardless of the OS.
Typically GPIO drivers have been handling this by using #gpio-cells
set to 2, and using the 2nd cell for flags. The priority can be
encoded as a flag.
g.
--
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.
^ permalink raw reply
* Re: [RFC] misc/at24: add experimental OF support for the generic eeprom driver
From: Nate Case @ 2009-10-09 14:01 UTC (permalink / raw)
To: Grant Likely; +Cc: devicetree-discuss, linux-embedded, linuxppc-dev, linux-i2c
In-Reply-To: <fa686aa40910082240w7fb2a194tcc80e7afe182e781@mail.gmail.com>
On Thu, 2009-10-08 at 23:40 -0600, Grant Likely wrote:
> For your future reference, patches that look at the device tree should
> also cc: devicetree-discuss@lists.ozlabs.org so that new bindings can
> be reviewed and common mistakes can be avoided. It is expected that
> new device tree bindings are accompanied with documentation describing
> what the binding is for and how it should be used (see
> Documentation/powerpc/dts-bindings).
>
> I know this change is already in mainline, but can you please post the
> device tree fragment that you're using to describe this chip? I want
> to make sure we don't get stuck with things in the kernel that will be
> hard to maintain in the long term.
Hi Grant,
Sorry for neglecting to include devicetree-discuss on that one. I was
in fact aware of this list, and subscribe to it. I really just forgot
in this case.
I also have a documentation patch for it that went along with it, but it
wasn't ready in time and so it's been sitting in our local patch queue.
I can submit that soon, but it probably makes sense for Wolfram to
voice whatever his concerns were about "questionable" properties before
I document what's there.
Here's an example device tree node for this case:
gpio1: gpio@18 {
compatible = "nxp,pca9557";
reg = <0x18>;
#gpio-cells = <2>;
gpio-controller;
polarity = <0x00>;
};
In this case, the linux,gpio-base property wasn't specified. But, the
use case is identical to the pdata->gpio_base field. "polarity" is used
for specifying polarity inversion for each line, and is in the same
format of the 'polarity inversion' register on the chip. My reasoning
in the property naming was as follows:
linux,gpio-base: Linux-specific as it relates to internal GPIO
numbering. So, it's prefixed with "linux,"
polarity: Dictated by how hardware is wired up, so it's needed
regardless of the OS.
- Nate
^ permalink raw reply
* Re: [RFC] misc/at24: add experimental OF support for the generic eeprom driver
From: Nate Case @ 2009-10-09 13:43 UTC (permalink / raw)
To: Wolfram Sang; +Cc: devicetree-discuss, linux-embedded, linuxppc-dev, linux-i2c
In-Reply-To: <20091009051409.GA2361@pengutronix.de>
On Fri, 2009-10-09 at 07:14 +0200, Wolfram Sang wrote:
> And while doing this and figuring the pro/cons of those methods, I
> stumbled over this commit:
>
> gpio: pca953x: Get platform_data from OpenFirmware
> (1965d30356c1c65660ba3330927671cfe81acdd5)
Aside from any issues you have with the properties themselves, what's
your take on this approach?
Personally, I just got tired of waiting for someone else to solve the
pdata/OF problem. So I submitted that commit as an attempt at something
very simple and unobtrusive to the device driver itself. It seems
pretty clean to me, but I'm curious to see if others have any better
ideas.
- Nate
^ permalink raw reply
* Re: [RFC] misc/at24: add experimental OF support for the generic eeprom driver
From: Grant Likely @ 2009-10-09 6:37 UTC (permalink / raw)
To: avorontsov; +Cc: linuxppc-dev, devicetree-discuss, linux-embedded, linux-i2c
In-Reply-To: <20091008222042.GA10743@oksana.dev.rtsoft.ru>
On Thu, Oct 8, 2009 at 4:20 PM, Anton Vorontsov
<avorontsov@ru.mvista.com> wrote:
> On Thu, Oct 08, 2009 at 09:48:50AM -0600, Grant Likely wrote:
>> But the focus is still on creating pdata. If a translator gets too
>> big, then sure, split it into a separate file. Until then, there I
>> see no good reason to do so now.
>
> Luckily, I'm not at24 driver maintainer (Wolfram himself is ;-),
> but as a maintainer of driver "Foo", I would not want to see
> completely unfamiliar "Bar" in my shiny driver.
It sounds like your saying that data parsing isn't really the same as
driver code, and I don't think that is true. Device data parsing is
equally as important as the functional behaviour. A device driver
isn't complete unless it does both. Right now most drivers only
understand LInux's internal representation (pdata) because that is the
only data source they've needed to this point. When new data sources
appear (device tree), it is completely appropriate for the driver to
be modified to understand the new data format (with all the caveats of
coding it in a logical way with translation decoupled from function to
keep impact at a bare minimum).
To use your example, a driver author who states "I only use Bar; so I
don't ever want to see Foo code" is probably being a bit short sighted
with regards to portability.
> Another plus is that you can bypass (or almost bypass) subsystem
> maintainers when merging OF-specific patches (since he/she couldn't
> possibly care less about all these weird arch internals. But again,
> this doesn't work for this particular driver since Wolfram is the
> maintainer :-).
I don't want OF parsing to bypass subsystem or driver maintainers.
:-) I think they should be involved in reviewing and acking
translator code.
>> > If I wasn't a PPC/OF guy to some degree, I'd hate PPC/OF people
>> > for bringing arch-specific details into a generic code... :-P
>>
>> No, this goes beyond PPC/OF. The real issue is that it is no longer a
>> safe assumption that pdata will be a static data structure in platform
>> code. The number of possible data sources is going to get larger, not
>> smaller. OF is just one. UEFI is another. Translating that data
>> into pdata will be the problem that comes up over and over again.
>> However, translation code is still driver specific,
>> so it belongs with the driver that it translates code for.
>
> Wait... The translation code depends on a platform, and on a
> platform_data structure, the same as non-OF arch-specific code
> depends on it.
The translation code depends on the data source. That may be OF. It
may be UEFI. It may be another driver (think a PCI driver
instantiating a set of child platform devices). It may be a kernel
hacker (who hard codes it into the platform code). It has nothing to
do with arch/. (and ignore the whole of_platform bus stuff; that was
a bad idea from the outset (not that we knew that at the time). I
don't intend to port of_platform to other architectures).
> How is it different from a static platform data
> in the arch/ code? We don't put static platform data into the
> drivers and surround them with ugly #ifdefs+machine_is()...
Of course we don't put static platform data into the drivers; because
static platform data is platform specific, and therefore belongs with
the platform. An OF translator is different from a static pdata
because it is driver specific code instead of platform specific code.
Platform specific code is only applicable for the platform, so of
course it belongs with the platform code. Driver specific code
belongs with the driver because it isn't applicable to anything else.
The whole point of the device tree is that it allows driver specific
code to be written that understands the data describing the device
instead of having a programmer hard code it.
>
>> So, in my opinion, translation code must:
>> 1. be *tiny*
>
> Yeah, dream on. ;-) It's tiny when all you have is of_get_property(),
> I'd like to see the code when you'll have GPIOs, IRQs, and platform-
> specific fixups.
It's still pretty tiny, because it still needs to populate a pdata
structure. 99% of the time there won't be any platform specific
fixups either.... In the odd case when there *are* platform specific
hacks, then of course the pdata should be created by platform code,
and not driver code. One way to do this is to have platform code hook
into the notifier call chain for a bus and watch for devices it needs
to meddle with. When one shows up, register the custom pdata before
the driver gets probed. But that is the special case, which doesn't
need to impact the common case.
> You might say that at24 doesn't need that stuff, but it does.
> Suppose AT24's WP pin is connected to a GPIO, and without
> 'read-only' property I'd like the driver to pull the pin low,
> and vice versa: with 'read-only' specifier, WP should be tied
> high. Or if WP is controlled by a switch/jumper, GPIO can be
> used to read current WP state.
I still don't see a problem. If it can be described in pdata, then a
translator function can populate it from the device tree data. It
still isn't huge. Besides, just because it *might* someday become
huge doesn't mean that it should be segregated into another file now.
It can always be moved later if it becomes too big.
>> -- should be trivial to add to a driver without impacting
>> common code
>
> This is doable, yes.
>
>> > No matter how small the OF code is, I believe we shouldn't put it
>> > into the generic code. Take a look at mmc_spi case again, it can be
>> > easily extended to any arch, because there is no arch-specific stuff,
>> > but a "get/put" pattern for platform data.
>>
>> I'm not disagreeing with you that the arch specific stuff should be
>> logically separated from the generic code. But I don't agree that it
>> belongs in a separate file. And I also think that the mmc_spi
>> implementation uses too much code. There must be a better way.
>
> I wonder how you'd shrink the mmc_spi bindings, can you elaborate?
To start; eliminate all the pdata management code and write some
library routines to do that instead. Next, I'd refactor the code to
separate out the GPIO handling stuff because the GPIO handling really
isn't related to OF at all (that code could just as easily be used by
a static pdata structure definition). All that should be left is the
meat of the mmc_spi_get_pdata() function which parses the device tree
and populates pdata.
I agree that the infrastructure to do what I'm suggesting doesn't
exist yet; but I say this because I think it is the direction that
device tree support needs to go.
g.
--
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.
^ permalink raw reply
* Re: [RFC] misc/at24: add experimental OF support for the generic eeprom driver
From: Grant Likely @ 2009-10-09 5:40 UTC (permalink / raw)
To: Wolfram Sang, Nate Case
Cc: linuxppc-dev-mnsaURCQ41sdnm+yROfE0A,
avorontsov-hkdhdckH98+B+jHODAdFcQ,
devicetree-discuss-mnsaURCQ41sdnm+yROfE0A,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
linux-embedded-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <20091009051409.GA2361-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
On Thu, Oct 8, 2009 at 11:14 PM, Wolfram Sang <w.sang-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org> wrote:
>> Will check this tomorrow.
>
> And while doing this and figuring the pro/cons of those methods, I stumbled over this commit:
>
> gpio: pca953x: Get platform_data from OpenFirmware
> (1965d30356c1c65660ba3330927671cfe81acdd5)
>
> It looks to me that it missed all people involved in OF/DT-development and now we
> have undocumented and IMO questionable properties in the kernel.
Hi Nate,
For your future reference, patches that look at the device tree should
also cc: devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ@public.gmane.org so that new bindings can
be reviewed and common mistakes can be avoided. It is expected that
new device tree bindings are accompanied with documentation describing
what the binding is for and how it should be used (see
Documentation/powerpc/dts-bindings).
I know this change is already in mainline, but can you please post the
device tree fragment that you're using to describe this chip? I want
to make sure we don't get stuck with things in the kernel that will be
hard to maintain in the long term.
Thanks,
g.
--
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.
^ permalink raw reply
* Re: [RFC] misc/at24: add experimental OF support for the generic eeprom driver
From: Wolfram Sang @ 2009-10-09 5:14 UTC (permalink / raw)
To: Grant Likely; +Cc: linuxppc-dev, devicetree-discuss, linux-i2c, linux-embedded
In-Reply-To: <20091008202723.GA8116@pengutronix.de>
[-- Attachment #1.1: Type: text/plain, Size: 775 bytes --]
> Will check this tomorrow.
And while doing this and figuring the pro/cons of those methods, I stumbled over this commit:
gpio: pca953x: Get platform_data from OpenFirmware
(1965d30356c1c65660ba3330927671cfe81acdd5)
It looks to me that it missed all people involved in OF/DT-development and now we
have undocumented and IMO questionable properties in the kernel.
Conclusions I draw:
a) we better solve the pdata-problem rather sooner than later ;)
b) we need to spread the word about devicetree-discuss
c) more documentation may help, too
I know, 'send patches'...
Regards,
Wolfram
--
Pengutronix e.K. | Wolfram Sang |
Industrial Linux Solutions | http://www.pengutronix.de/ |
[-- Attachment #1.2: Digital signature --]
[-- Type: application/pgp-signature, Size: 197 bytes --]
[-- Attachment #2: Type: text/plain, Size: 150 bytes --]
_______________________________________________
Linuxppc-dev mailing list
Linuxppc-dev@lists.ozlabs.org
https://lists.ozlabs.org/listinfo/linuxppc-dev
^ permalink raw reply
* Re: [RFC] misc/at24: add experimental OF support for the generic eeprom driver
From: Anton Vorontsov @ 2009-10-08 22:20 UTC (permalink / raw)
To: Grant Likely
Cc: linuxppc-dev-mnsaURCQ41sdnm+yROfE0A,
devicetree-discuss-mnsaURCQ41sdnm+yROfE0A,
linux-embedded-u79uwXL29TY76Z2rM5mHXA,
linux-i2c-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <fa686aa40910080848r459c47baob73fc70a95a08604-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
On Thu, Oct 08, 2009 at 09:48:50AM -0600, Grant Likely wrote:
> On Thu, Oct 8, 2009 at 9:10 AM, Anton Vorontsov
[...]
> > It's *always* a small amound of code, at a start. Then we get
> > floppy disk drivers and the tty layer. ;-)
>
> Holy straw man argument Batman!
>
> But the focus is still on creating pdata. If a translator gets too
> big, then sure, split it into a separate file. Until then, there I
> see no good reason to do so now.
Luckily, I'm not at24 driver maintainer (Wolfram himself is ;-),
but as a maintainer of driver "Foo", I would not want to see
completely unfamiliar "Bar" in my shiny driver.
Another plus is that you can bypass (or almost bypass) subsystem
maintainers when merging OF-specific patches (since he/she couldn't
possibly care less about all these weird arch internals. But again,
this doesn't work for this particular driver since Wolfram is the
maintainer :-).
> > If I wasn't a PPC/OF guy to some degree, I'd hate PPC/OF people
> > for bringing arch-specific details into a generic code... :-P
>
> No, this goes beyond PPC/OF. The real issue is that it is no longer a
> safe assumption that pdata will be a static data structure in platform
> code. The number of possible data sources is going to get larger, not
> smaller. OF is just one. UEFI is another. Translating that data
> into pdata will be the problem that comes up over and over again.
> However, translation code is still driver specific,
> so it belongs with the driver that it translates code for.
Wait... The translation code depends on a platform, and on a
platform_data structure, the same as non-OF arch-specific code
depends on it. How is it different from a static platform data
in the arch/ code? We don't put static platform data into the
drivers and surround them with ugly #ifdefs+machine_is()...
> So, in my opinion, translation code must:
> 1. be *tiny*
Yeah, dream on. ;-) It's tiny when all you have is of_get_property(),
I'd like to see the code when you'll have GPIOs, IRQs, and platform-
specific fixups.
You might say that at24 doesn't need that stuff, but it does.
Suppose AT24's WP pin is connected to a GPIO, and without
'read-only' property I'd like the driver to pull the pin low,
and vice versa: with 'read-only' specifier, WP should be tied
high. Or if WP is controlled by a switch/jumper, GPIO can be
used to read current WP state.
> -- should be trivial to add to a driver without impacting
> common code
This is doable, yes.
> > No matter how small the OF code is, I believe we shouldn't put it
> > into the generic code. Take a look at mmc_spi case again, it can be
> > easily extended to any arch, because there is no arch-specific stuff,
> > but a "get/put" pattern for platform data.
>
> I'm not disagreeing with you that the arch specific stuff should be
> logically separated from the generic code. But I don't agree that it
> belongs in a separate file. And I also think that the mmc_spi
> implementation uses too much code. There must be a better way.
I wonder how you'd shrink the mmc_spi bindings, can you elaborate?
Thanks,
--
Anton Vorontsov
email: cbouatmailru-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org
irc://irc.freenode.net/bd2
^ permalink raw reply
* Re: [RFC] misc/at24: add experimental OF support for the generic eeprom driver
From: Wolfram Sang @ 2009-10-08 20:27 UTC (permalink / raw)
To: Grant Likely; +Cc: linux-embedded, linuxppc-dev, linux-i2c, devicetree-discuss
In-Reply-To: <fa686aa40910080848r459c47baob73fc70a95a08604@mail.gmail.com>
[-- Attachment #1.1: Type: text/plain, Size: 1250 bytes --]
> No, this goes beyond PPC/OF. The real issue is that it is no longer a
> safe assumption that pdata will be a static data structure in platform
> code. The number of possible data sources is going to get larger, not
> smaller. OF is just one. UEFI is another. Translating that data
> into pdata will be the problem that comes up over and over again.
> However, translation code is still driver specific, so it belongs with
> the driver that it translates code for.
>
> So, in my opinion, translation code must:
> 1. be *tiny* -- should be trivial to add to a driver without impacting
> common code
> 2. live with the driver that it translates data for; ideally in the
> same .c file for drivers that are small.
I am with Grant on these points. It is more than just PPC.
> > No matter how small the OF code is, I believe we shouldn't put it
> > into the generic code. Take a look at mmc_spi case again, it can be
> > easily extended to any arch, because there is no arch-specific stuff,
> > but a "get/put" pattern for platform data.
Will check this tomorrow.
--
Pengutronix e.K. | Wolfram Sang |
Industrial Linux Solutions | http://www.pengutronix.de/ |
[-- Attachment #1.2: Digital signature --]
[-- Type: application/pgp-signature, Size: 197 bytes --]
[-- Attachment #2: Type: text/plain, Size: 150 bytes --]
_______________________________________________
Linuxppc-dev mailing list
Linuxppc-dev@lists.ozlabs.org
https://lists.ozlabs.org/listinfo/linuxppc-dev
^ permalink raw reply
* Re: [RFC] misc/at24: add experimental OF support for the generic eeprom driver
From: Grant Likely @ 2009-10-08 15:48 UTC (permalink / raw)
To: avorontsov; +Cc: linuxppc-dev, devicetree-discuss, linux-embedded, linux-i2c
In-Reply-To: <20091008151007.GA21328@oksana.dev.rtsoft.ru>
On Thu, Oct 8, 2009 at 9:10 AM, Anton Vorontsov
<avorontsov@ru.mvista.com> wrote:
> On Thu, Oct 08, 2009 at 08:53:46AM -0600, Grant Likely wrote:
> [...]
>> Please don't. It is such a small amount of code,
>
> It's *always* a small amound of code, at a start. Then we get
> floppy disk drivers and the tty layer. ;-)
Holy straw man argument Batman!
But the focus is still on creating pdata. If a translator gets too
big, then sure, split it into a separate file. Until then, there I
see no good reason to do so now.
>
> [...]
>> Driver writers shouldn't have to
>> write anything more than a tiny function to populate pdata from the
>> device tree. Managing that pdata instance needs to be done with
>> common infrastructure (but I don't have a firm idea about how it
>> should look yet). In the mean time I think Wolfram's approach has
>> lower impact.
>
> If I wasn't a PPC/OF guy to some degree, I'd hate PPC/OF people
> for bringing arch-specific details into a generic code... :-P
No, this goes beyond PPC/OF. The real issue is that it is no longer a
safe assumption that pdata will be a static data structure in platform
code. The number of possible data sources is going to get larger, not
smaller. OF is just one. UEFI is another. Translating that data
into pdata will be the problem that comes up over and over again.
However, translation code is still driver specific, so it belongs with
the driver that it translates code for.
So, in my opinion, translation code must:
1. be *tiny* -- should be trivial to add to a driver without impacting
common code
2. live with the driver that it translates data for; ideally in the
same .c file for drivers that are small.
> No matter how small the OF code is, I believe we shouldn't put it
> into the generic code. Take a look at mmc_spi case again, it can be
> easily extended to any arch, because there is no arch-specific stuff,
> but a "get/put" pattern for platform data.
I'm not disagreeing with you that the arch specific stuff should be
logically separated from the generic code. But I don't agree that it
belongs in a separate file. And I also think that the mmc_spi
implementation uses too much code. There must be a better way.
g.
--
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.
^ permalink raw reply
* [ANNOUNCE] Embedded Linux Conference Europe Program Announced
From: Tim Bird @ 2009-09-29 16:38 UTC (permalink / raw)
To: linux-embedded
Sorry in advance for this non-technical post, but I figured
some embedded kernel developers (especially in Europe) might
be interested in this.
== SUMMARY
The CE Linux Forum is happy to announce the program for
Embedded Linux Conference Europe, 2009. This event, located
this year in Grenoble, France, brings together embedded Linux
experts from Europe and around the world.
A full list of sessions is available at:
http://www.embeddedlinuxconference.com/elc_europe09/sessions.html
and for program details, see:
http://www.embeddedlinuxconference.com/elc_europe09/program.html
Registration is now open at the above web site.
== PROGRAM HIGHLIGHTS
Authors of some of the most useful and important resource books
in the Linux industry will be there, as well as developers and
experts from companies like: Philips, Sony, ST Microelectronics,
Free Electrons, Mentor Graphics MontaVista, Pengutronix, and Wind River.
The conference will host over 40 sessions, including presentations,
Birds-of-a-Feather sessions, keynotes and tutorials. The sessions
will cover such wide-ranging topics as:
* Board bring-up and porting
* Realtime performance
* Core kernel features
* Graphics and video
* Fast booting
* Tracing
* Embedded distributions and build systems
* Bootloaders and bootloader interfaces (device tree)
* Security
* Android !!
and much more.
Here are some of the embedded Linux experts presenting at
this event:
* Jon Masters - Kernel hacker and co-author of "Building Embedded
Linux Systems"
* Phillipe Gerum - Founder and lead maintainer of the Adeos and
Xenomai projects. Also a co-author of "Building Embedded Linux Systems"
* Allessandro Rubini - Author of 1st edition of "Linux Device Drivers"
* Gilad Ben-Yossef - Co-founder of Codefidence LTD, and co-author of
"Building Embedded Linux Systems"
* Matt Porter - Long-term kernel contributor, now at Mentor Graphics
and previously at MontaVista Software and Motorola Computer Group.
* Alex de Vries - Chief Technologist for Wind River Systems
* Frank Rowand - Realtime Linux practitioner at Sony Corporation
Registration includes access to all sessions, demos, BOF meetings,
lunch and snacks each day, and a fun social event Thursday night.
To register, please follow the link at:
http://www.embeddedlinuxconference.com/elc_europe09/registration1.html
If you have any questions, send e-mail to elce-09@tree.celinuxforum.org.
I hope you can make it!
-- Tim
=============================
Tim Bird
Architecture Group Chair, CE Linux Forum
Senior Staff Engineer, Sony Corporation of America
=============================
^ permalink raw reply
* Re: GPIO driver for MPC8313.
From: Peter Korsgaard @ 2009-09-28 5:16 UTC (permalink / raw)
To: Johnny Hung; +Cc: linuxppc-dev, linux-embedded
In-Reply-To: <cb9ecdfa0909271943o6133f5c8r9b25a25140e7842f@mail.gmail.com>
>>>>> "Johnny" == Johnny Hung <johnny.hacking@gmail.com> writes:
Johnny> The kerne source I used now is 2.6.23 with MPC8313-erdb
Johnny> pached. I think I should port 2.6.28 gpio function back to
Johnny> 2.6.23. Is it a common way to implement it in the kernel I used
Johnny> or I should port MPC8313-erdb pached to 2.6.28 opposite?
It shouldn't be that hard to back port, but I would certainly go for
2.6.28 (or rather 2.6.31) unless something keeps you at 2.6.23.
--
Bye, Peter Korsgaard
^ permalink raw reply
* Re: GPIO driver for MPC8313.
From: Johnny Hung @ 2009-09-28 2:43 UTC (permalink / raw)
To: Peter Korsgaard; +Cc: linuxppc-dev, linux-embedded
In-Reply-To: <cb9ecdfa0909230820k6ebbd589o409e58e2d9b6740c@mail.gmail.com>
The kerne source I used now is 2.6.23 with MPC8313-erdb pached. I
think I should port 2.6.28 gpio function back to 2.6.23. Is it a
common way to implement it in the kernel I used or I should port
MPC8313-erdb pached to 2.6.28 opposite?
BRs, H. Johnny
2009/9/23 Johnny Hung <johnny.hacking@gmail.com>:
> Many thanks for your help. I will try it.
>
> 2009/9/23 Peter Korsgaard <jacmet@sunsite.dk>:
>>>>>>> "Johnny" == Johnny Hung <johnny.hacking@gmail.com> writes:
>>
>> Johnny> Thanks, got it. BTW, how to trigger GPIO level in user space
>> Johnny> application? I also found
>> Johnny> arch/powerpc/platforms/52xx/mpc52xx_gpio.c is a good
>> Johnny> example. Any reply is appreciate.
>>
>> Through sysfs. See 'Sysfs Interface for Userspace' section of
>> Documentation/gpio.txt
>>
>> --
>> Bye, Peter Korsgaard
>>
>
^ permalink raw reply
* Re: GPIO driver for MPC8313.
From: Johnny Hung @ 2009-09-23 15:20 UTC (permalink / raw)
To: Peter Korsgaard; +Cc: linuxppc-dev, linux-embedded
In-Reply-To: <87r5txrkfz.fsf@macbook.be.48ers.dk>
Many thanks for your help. I will try it.
2009/9/23 Peter Korsgaard <jacmet@sunsite.dk>:
>>>>>> "Johnny" == Johnny Hung <johnny.hacking@gmail.com> writes:
>
> Johnny> Thanks, got it. BTW, how to trigger GPIO level in user space
> Johnny> application? I also found
> Johnny> arch/powerpc/platforms/52xx/mpc52xx_gpio.c is a good
> Johnny> example. Any reply is appreciate.
>
> Through sysfs. See 'Sysfs Interface for Userspace' section of
> Documentation/gpio.txt
>
> --
> Bye, Peter Korsgaard
>
^ permalink raw reply
* linux booting fails on ppc440x5
From: Vineeth @ 2009-09-23 14:34 UTC (permalink / raw)
To: linux-embedded
I am trying to port linux on IBM powerpc-440x5. I have this board
which has this processor, a 16MB SRAM sits on location 0x0, uart and a
flash.I have a simple bootloader which does the following.
1. Initialize the processor (as part of it, we Generates the tlbs
for UART,16MB flash,16MB SRAM)
2. Initialize the UART
3. Copy the simple-boot linux_image (binary file) from flash to
0x400000 location of SRAM.
4. Kernel entry to 0x400000
I also have a device tree structure file (image.dts) which i am
passing while generating the linux image.
It all went through and linux got extracted to the 0x0th location of
SRAM. and started executing.
(plz find the log below)
it always crashed @ "Unable to handle kernel paging request for data
at address 0x0xxxxxxx"
is there anything i have to change in the boot loader or kernel with
respect to the MMU initialization ?
in the kernel code @ /arch/powerpc/kernel/head_44x.s; there is a
comment saying "its trying to invalidate all the TLB entries except
the one it currently working on". Will it make any issues as in our
case we have only one TLB for the entire 16MB sram, which will be the
current working one..
Can someone suggest us some clue or details on this.
Thanks & Regards,
Vineeth
LINUX BOOT LOG
----------------------------------------------------------------------------------------
Initialized the System
Initialized the UART
Copying Linux Image to RAM > !!!!!!!!!!!!!!!!!!!!!!!!!!!
Copying Image Done
-KERNEL ENTRY-
-
zImage starting: loaded at 0x00400000 (sp: 0x004deeb0)
Allocating 0x1dad84 bytes for kernel ...
gunzipping (0x00000000 <- 0x0040c000:0x004dd3f1)...done 0x1c31cc bytes
Linux/PowerPC load: console=ttyS0 root=/dev/ram
Finalizing device tree... flat tree at 0x4eb300
id mach(): done
inside skybeam_register_console function
MMU:enterMMU:hw initMMU:mapinMMU:setioMMU:exitinside
_setup_arch-begininginside _setup_arch-1inside
_setup_arch-2setup_arch: bootmemarch: exit<7>Top of RAM: 0x1000000,
Total RAM: 0x1000000
Zone PFN ranges:
DMA 0x00000000 -> 0x00001000
Normal 0x00001000 -> 0x00001000
Movable zone start PFN for each node
early_node_map[1] active PFN ranges
0: 0x00000000 -> 0x00001000
MMU: Allocated 1088 bytes of context maps for 255 contexts
Built 1 zonelists in Zone order, mobility grouping off. Total pages: 4064
Kernel command line: console=ttyS0 root=/dev/ram
Unable to handle kernel paging request for data at address 0x00021000
Faulting instruction address: 0xc010a7c4
Oops: Kernel access of bad area, sig: 11 [#1]
PREEMPT PowerPC 44x Platform
Modules linked in:
NIP: c010a7c4 LR: c010dc50 CTR: 00000000
REGS: c01bfeb0 TRAP: 0300 Not tainted (2.6.30)
MSR: 00021000 <ME,CE> CR: 24000044 XER: 00000000
DEAR: 00021000, ESR: 00000000
TASK = c01a94b8[0] 'swapper' THREAD: c01be000
GPR00: 00001180 c01bff60 c01a94b8 00021000 00000025 00000008 c0104968 00000000
GPR08: 2f72616d c0110000 c0155938 c01a0000 22000024 00000000 fffff104 00000000
GPR16: 00000000 00000000 00000000 00000000 fffffff8 000008b8 c010d758 c0104968
GPR24: 00001198 00001190 c018a001 c01c5498 000008c0 00001188 00021000 c01c42f0
NIP [c010a7c4] strchr+0x0/0x3c
LR [c010dc50] match_token+0x138/0x228
Call Trace:
[c01bff60] [c016b99c] 0xc016b99c (unreliable)
[c01bffa0] [c0104a00] sort_extable+0x28/0x38
[c01bffb0] [c01938ec] sort_main_extable+0x20/0x30
[c01bffc0] [c018c734] start_kernel+0x140/0x288
[c01bfff0] [c0000200] skpinv+0x190/0x1cc
Instruction dump:
7ca903a6 88040000 38a5ffff 38840001 2f800000 98090000 39290001 419e0010
4200ffe4 98a90000 4e800020 4e800020 <88030000> 5484063e 7f802000 4d9e0020
---[ end trace 31fd0ba7d8756001 ]---
Kernel panic - not syncing: Attempted to kill the idle task!
Call Trace:
[c01bfd90] [c0005d5c] show_stack+0x4c/0x16c (unreliable)
[c01bfdd0] [c002f17c] panic+0xa0/0x168
[c01bfe20] [c0032eb8] do_exit+0x61c/0x638
[c01bfe60] [c000b60c] kernel_bad_stack+0x0/0x4c
[c01bfe90] [c000f310] bad_page_fault+0x90/0xd8
[c01bfea0] [c000e184] handle_page_fault+0x7c/0x80
[c01bff60] [c016b99c] 0xc016b99c
[c01bffa0] [c0104a00] sort_extable+0x28/0x38
[c01bffb0] [c01938ec] sort_main_extable+0x20/0x30
[c01bffc0] [c018c734] start_kernel+0x140/0x288
[c01bfff0] [c0000200] skpinv+0x190/0x1cc
Rebooting in 180 seconds..
^ permalink raw reply
* Re: GPIO driver for MPC8313.
From: Peter Korsgaard @ 2009-09-23 11:04 UTC (permalink / raw)
To: Johnny Hung; +Cc: linuxppc-dev, linux-embedded
In-Reply-To: <cb9ecdfa0909230352w77a37f59h3c7f1e146121f9f6@mail.gmail.com>
>>>>> "Johnny" == Johnny Hung <johnny.hacking@gmail.com> writes:
Johnny> Thanks, got it. BTW, how to trigger GPIO level in user space
Johnny> application? I also found
Johnny> arch/powerpc/platforms/52xx/mpc52xx_gpio.c is a good
Johnny> example. Any reply is appreciate.
Through sysfs. See 'Sysfs Interface for Userspace' section of
Documentation/gpio.txt
--
Bye, Peter Korsgaard
^ permalink raw reply
* Re: GPIO driver for MPC8313.
From: Johnny Hung @ 2009-09-23 10:52 UTC (permalink / raw)
To: Peter Korsgaard; +Cc: linuxppc-dev, linux-embedded
In-Reply-To: <87y6o6rvvc.fsf@macbook.be.48ers.dk>
Thanks, got it. BTW, how to trigger GPIO level in user space
application? I also found arch/powerpc/platforms/52xx/mpc52xx_gpio.c
is a good example. Any reply is appreciate.
BRs, H. Johnny
2009/9/23 Peter Korsgaard <jacmet@sunsite.dk>:
>>>>>> "Johnny" == Johnny Hung <johnny.hacking@gmail.com> writes:
>
> Johnny> Hi All:
> Johnny> Is there a alreday written GPIO dirver or example for
> Johnny> MPC8313/similar ppc platform. It looks like many people need GPIO
> Johnny> dirver to control LED, etc... I think is it possible to write a
> Johnny> general gpio driver for all ppc platform and only need to modify gpio
> Johnny> iomap information of dtb file. Please give me a advice. Thanks in
> Johnny> advanced.
>
> Sure, it's arch/powerpc/sysdev/mpc8xxx_gpio.c, included since 2.6.28. To
> use it, simply enable CONFIG_MPC8xxx_GPIO and add a gpio-controller node
> to your dts, similar to how it's done in
> arch/powerpc/boot/dts/mpc837*_rdb.dts.
>
> See Documentation/powerpc/dts-bindings/fsl/8xxx_gpio.txt for details of
> the dts bindings.
>
> --
> Bye, Peter Korsgaard
>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox