Embedded Linux development
 help / color / mirror / Atom feed
* [PWM PATCH 5/5] LED "dimmer" trigger
From: Bill Gatliff @ 2010-02-02  7:14 UTC (permalink / raw)
  To: linux-embedded; +Cc: linux-kernel, Bill Gatliff
In-Reply-To: <cover.1265094517.git.bgat@billgatliff.com>


Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
 drivers/leds/ledtrig-dim.c |   95 ++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 95 insertions(+), 0 deletions(-)
 create mode 100644 drivers/leds/ledtrig-dim.c

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");
-- 
1.6.5

^ permalink raw reply related

* [PWM PATCH 4/5] PWM-based LED control
From: Bill Gatliff @ 2010-02-02  7:14 UTC (permalink / raw)
  To: linux-embedded; +Cc: linux-kernel, Bill Gatliff
In-Reply-To: <cover.1265094517.git.bgat@billgatliff.com>


Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
 drivers/leds/leds-pwm.c     |  224 ++++++++++++++++++++++++-------------------
 include/linux/pwm/pwm-led.h |   34 +++++++
 2 files changed, 158 insertions(+), 100 deletions(-)
 create mode 100644 include/linux/pwm/pwm-led.h

diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c
index 88b1dd0..7877af8 100644
--- a/drivers/leds/leds-pwm.c
+++ b/drivers/leds/leds-pwm.c
@@ -1,152 +1,176 @@
 /*
- * linux/drivers/leds-pwm.c
+ * drivers/leds/leds-pwm.c
  *
- * simple PWM based LED control
+ * Copyright (C) 2010 Bill Gatliff <bgat@billgatliff.com>
+ * Copyright (C) 2009 Loutao Fu, Pengutronix <l.fu@pengutronix.de>
  *
- * Copyright 2009 Luotao Fu @ Pengutronix (l.fu@pengutronix.de)
+ * Based on leds-gpio.c by Raphael Assenat <raph@8d.com>
  *
- * 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
+ * This program is Free Software.  You may redistribute 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/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/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;
+#include <linux/io.h>
+#include <linux/pwm/pwm.h>
+#include <linux/pwm/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 inline struct led_pwm *to_led_pwm(const struct led_classdev *c)
+{
+	return container_of(c, struct led_pwm, led);
+}
+
+static void
+led_pwm_brightness_set(struct led_classdev *c,
+		       enum led_brightness b)
+{
+	struct led_pwm *led = to_led_pwm(c);
+	int percent;
+
+	percent = (b * 100) / (LED_FULL - LED_OFF);
+	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 *led = to_led_pwm(c);
+	return led->percent;
+}
+
+static int
+led_pwm_blink_set(struct led_classdev *c,
+		  unsigned long *on_ms,
+		  unsigned long *off_ms)
 {
-	struct led_pwm_data *led_dat =
-		container_of(led_cdev, struct led_pwm_data, cdev);
-	unsigned int max = led_dat->cdev.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 = to_led_pwm(c);
+	struct pwm_channel_config cfg;
+
+	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 __devinit
+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)
+	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->period = cur_led->pwm_period_ns;
-		led_dat->cdev.brightness_set = led_pwm_set;
-		led_dat->cdev.brightness = LED_OFF;
-		led_dat->cdev.max_brightness = cur_led->max_brightness;
-		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);
+
+	ret = led_classdev_register(&pdev->dev, &led->led);
+	if (ret < 0)
+		goto err_classdev_register;
 
-	kfree(leds_data);
+	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 __devexit
+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);
 
 	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/include/linux/pwm/pwm-led.h b/include/linux/pwm/pwm-led.h
new file mode 100644
index 0000000..92363c8
--- /dev/null
+++ b/include/linux/pwm/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.5

^ permalink raw reply related

* [PWM PATCH 3/5] Expunge old Atmel PWMC driver, replacing it with one that conforms to the PWM API
From: Bill Gatliff @ 2010-02-02  7:14 UTC (permalink / raw)
  To: linux-embedded; +Cc: linux-kernel, Bill Gatliff
In-Reply-To: <cover.1265094517.git.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  |  589 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 594 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 049ff24..8cf48cf 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -5,7 +5,11 @@
 obj-$(CONFIG_IBM_ASM)		+= ibmasm/
 obj-$(CONFIG_HDPU_FEATURES)	+= hdpuftrs/
 obj-$(CONFIG_AD525X_DPOT)	+= ad525x_dpot.o
-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..a2a08c5
--- /dev/null
+++ b/drivers/pwm/atmel-pwm.c
@@ -0,0 +1,589 @@
+/*
+ * drivers/pwm/atmel-pwm.c
+ *
+ * Copyright (C) 2010 Bill Gatliff <bgat@billgatliff.com>
+ * Copyright (C) 2007 David Brownell
+ *
+ * This program is free software; you may redistribute 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/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/pwm/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 struct atmel_pwm *to_atmel_pwm(const struct pwm_channel *p)
+{
+	return container_of(p->pwm, struct atmel_pwm, pwm);
+}
+
+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 = to_atmel_pwm(p);
+
+	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 = to_atmel_pwm(p);
+
+	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 = to_atmel_pwm(p);
+	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 = to_atmel_pwm(p);
+	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:
+	dev_dbg(p->pwm->dev, "sync_mask %x\n", 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 = to_atmel_pwm(p);
+
+	if (!to_p)
+		return;
+
+	ap->sync_mask[p->chan] |= (1 << to_p->chan);
+	ap->sync_mask[to_p->chan] |= (1 << p->chan);
+
+	dev_dbg(p->pwm->dev, "sync_mask %x\n", ap->sync_mask[p->chan]);
+}
+
+static inline void
+__atmel_pwm_stop(struct pwm_channel *p)
+{
+	struct atmel_pwm *ap = to_atmel_pwm(p);
+	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 = to_atmel_pwm(p);
+	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);
+
+	dev_dbg(p->pwm->dev, "polarity %d\n", 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);
+
+	dev_dbg(p->pwm->dev, "duty_ticks = %lu cprd = %x"
+		" cdty = %x cpre = %x\n", 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;
+
+	dev_dbg(p->pwm->dev, "period_ticks = %lu cprd = %x cpre = %x\n",
+		 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);
+		break;
+
+	case PWM_CONFIG_START:
+		__atmel_pwm_start(p);
+		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);
+			
+			dev_dbg(p->pwm->dev, "waiting on stop_sync completion...\n");
+			
+			ret = wait_for_completion_interruptible(&p->complete);
+
+			dev_dbg(p->pwm->dev, "stop_sync complete (%d)\n", ret);
+
+			if (ret)
+				return ret;
+		} while (p->flags & BIT(FLAG_STOP));
+	}
+
+	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();
+
+	dev_dbg(p->pwm->dev, "config_mask %x\n", 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);
+}
+
+static int
+atmel_pwm_set_callback(struct pwm_channel *p,
+		       pwm_callback_t callback)
+{
+	struct atmel_pwm *ap = to_atmel_pwm(p);
+	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 = to_atmel_pwm(p);
+	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 = to_atmel_pwm(p);
+	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 __devinit
+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)) {
+		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 __exit 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.5

^ permalink raw reply related

* [PWM PATCH 2/5] Emulates PWM hardware using a high-resolution timer and a GPIO pin
From: Bill Gatliff @ 2010-02-02  7:14 UTC (permalink / raw)
  To: linux-embedded; +Cc: linux-kernel, Bill Gatliff
In-Reply-To: <cover.1265094517.git.bgat@billgatliff.com>


Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
 drivers/pwm/gpio.c |  307 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 307 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..552ad1a
--- /dev/null
+++ b/drivers/pwm/gpio.c
@@ -0,0 +1,307 @@
+/*
+ * drivers/pwm/gpio.c
+ *
+ * Models a single-channel PWM device using a kernel interval timer
+ * and a GPIO pin.
+ *
+ * Copyright (C) 2010 Bill Gatliff <bgat@billgatliff.com>
+ *
+ * This program is free software; you may redistribute 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/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 inline struct gpio_pwm *to_gpio_pwm(const struct pwm_channel *p)
+{
+	return container_of(p->pwm, struct gpio_pwm, pwm);
+}
+
+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 = to_gpio_pwm(p);
+
+	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 = to_gpio_pwm(p);
+	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 = to_gpio_pwm(p);
+	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 = to_gpio_pwm(p);
+	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 = to_gpio_pwm(p);
+	gp->callback = callback;
+	return 0;
+}
+
+static int
+gpio_pwm_request(struct pwm_channel *p)
+{
+	p->tick_hz = 1000000000UL;
+	return 0;
+}
+
+static int __devinit
+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 __exit 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 a high-resolution timer");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:gpio_pwm");
-- 
1.6.5

^ permalink raw reply related

* [PWM PATCH 1/5] API to consolidate PWM devices behind a common user and kernel interface
From: Bill Gatliff @ 2010-02-02  7:14 UTC (permalink / raw)
  To: linux-embedded; +Cc: linux-kernel, Bill Gatliff
In-Reply-To: <cover.1265094517.git.bgat@billgatliff.com>


Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
 Documentation/pwm.txt   |  260 +++++++++++++++++++
 drivers/pwm/pwm.c       |  633 +++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/pwm.h     |   31 ---
 include/linux/pwm/pwm.h |  170 +++++++++++++
 4 files changed, 1063 insertions(+), 31 deletions(-)
 create mode 100644 Documentation/pwm.txt
 create mode 100644 drivers/pwm/pwm.c
 delete mode 100644 include/linux/pwm.h
 create mode 100644 include/linux/pwm/pwm.h

diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt
new file mode 100644
index 0000000..2c41ca5
--- /dev/null
+++ b/Documentation/pwm.txt
@@ -0,0 +1,260 @@
+                       Generic PWM Device API
+
+                          February 1, 2010
+                            Bill Gatliff
+                        <bgat@billgatliff.com>
+
+
+
+The code in drivers/pwm and include/linux/pwm/ 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 suddenly 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 accommodate 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, sensors and solenoids through a
+custom-designed, optically-isolated H-bridge driver.
+
+
+
+Functional Overview
+
+The Generic PWM Device API framework is implemented in
+include/linux/pwm/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 Generic PWM API framework also provides a sysfs interface to PWM
+devices, which is adequate for basic application needs and testing.
+
+Driver authors fill out a pwm_device structure, which describes the
+capabilities of the PWM hardware being constructed--- 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 as
+users begin to configure and utilize 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 accommodated 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.  By default, all PWM
+channels are unsynchronized.
+
+
+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 plain-text 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..f369384
--- /dev/null
+++ b/drivers/pwm/pwm.c
@@ -0,0 +1,633 @@
+/*
+ * drivers/pwm/pwm.c
+ *
+ * Copyright (C) 2010 Bill Gatliff <bgat@billgatliff.com>
+ *
+ * This program is free software; you may redistribute 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/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/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(*p), 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);
+
+	dev_info(pwm->dev, "%d channel%s\n", 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);
+	return &p->channels[chan];
+
+err_request_failed:
+	module_put(p->owner);
+err_module_get_failed:
+err_no_device:
+	mutex_unlock(&device_list_mutex);
+	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);
+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))
+		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:
+	dev_dbg(p->pwm->dev, "%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);
+	dev_dbg(p->pwm->dev, "handler %p scheduled with data %p\n",
+		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
deleted file mode 100644
index 7c77575..0000000
--- a/include/linux/pwm.h
+++ /dev/null
@@ -1,31 +0,0 @@
-#ifndef __LINUX_PWM_H
-#define __LINUX_PWM_H
-
-struct pwm_device;
-
-/*
- * pwm_request - request a PWM device
- */
-struct pwm_device *pwm_request(int pwm_id, const char *label);
-
-/*
- * pwm_free - free a PWM device
- */
-void pwm_free(struct pwm_device *pwm);
-
-/*
- * pwm_config - change a PWM device configuration
- */
-int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
-
-/*
- * pwm_enable - start a PWM output toggling
- */
-int pwm_enable(struct pwm_device *pwm);
-
-/*
- * pwm_disable - stop a PWM output toggling
- */
-void pwm_disable(struct pwm_device *pwm);
-
-#endif /* __LINUX_PWM_H */
diff --git a/include/linux/pwm/pwm.h b/include/linux/pwm/pwm.h
new file mode 100644
index 0000000..848cd76
--- /dev/null
+++ b/include/linux/pwm/pwm.h
@@ -0,0 +1,170 @@
+#ifndef __LINUX_PWM_H
+#define __LINUX_PWM_H
+
+/*
+ * 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
+ */
+
+#include <linux/completion.h>
+#include <linux/workqueue.h>
+#include <linux/spinlock.h>
+#include <linux/list.h>
+
+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_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);
+
+
+#endif /* __LINUX_PWM_H */
-- 
1.6.5

^ permalink raw reply related

* [PWM PATCH 0/5] Implements a common PWM API
From: Bill Gatliff @ 2010-02-02  7:14 UTC (permalink / raw)
  To: linux-embedded; +Cc: linux-kernel, Bill Gatliff

This patch series implements a common PWM API.  This series incorporates
the feedback from the linux-embedded mailing list and elsewhere; the author
greatly appreciates the efforts of everyone who reviewed the previous version
of this 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
  PWM-based LED control
  LED "dimmer" trigger

 Documentation/pwm.txt       |  260 ++++++++++++++++++
 drivers/leds/leds-pwm.c     |  224 +++++++++-------
 drivers/leds/ledtrig-dim.c  |   95 +++++++
 drivers/misc/Makefile       |    6 +-
 drivers/misc/atmel_pwm.c    |  409 ----------------------------
 drivers/pwm/atmel-pwm.c     |  589 ++++++++++++++++++++++++++++++++++++++++
 drivers/pwm/gpio.c          |  307 +++++++++++++++++++++
 drivers/pwm/pwm.c           |  633 +++++++++++++++++++++++++++++++++++++++++++
 include/linux/pwm.h         |   31 --
 include/linux/pwm/pwm-led.h |   34 +++
 include/linux/pwm/pwm.h     |  170 ++++++++++++
 11 files changed, 2217 insertions(+), 541 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/atmel-pwm.c
 create mode 100644 drivers/pwm/gpio.c
 create mode 100644 drivers/pwm/pwm.c
 delete mode 100644 include/linux/pwm.h
 create mode 100644 include/linux/pwm/pwm-led.h
 create mode 100644 include/linux/pwm/pwm.h

^ permalink raw reply

* Freescale MPC8313 TSEC SGMII problem.
From: Johnny Hung @ 2010-01-29  6:29 UTC (permalink / raw)
  To: linux-embedded, linuxppc-dev

Hi All,

I have a Freescale MPC8313 customized board and want to confiugre
TSEC1 to SGMII. Would you please help me for this question,

1). Is it possible for MPC8313 TSEC1 connect to Ethernet Switch by
SGMII (without phy), like below diagram.

[MPC8313 TSEC1]  <====SGMII====> [Ethernet Switch]

2). If yes in 1), how to configure it in MPC8313, as I know it can set
TSEC1 to SGMII in LTIB uboot setting. How about in Linux Kernel driver
? Is it need extra setting or patch?

BRs, H. Johnny

^ permalink raw reply

* Re: mount ramdisk rootfs /etc directory to jffs2 filesystem.
From: Johnny Hung @ 2010-01-25  8:43 UTC (permalink / raw)
  To: chris
  Cc: Marco Stornelli, Matthias Kaehlcke, Ricard Wanderlof,
	kernelnewbies, linux-mtd@lists.infradead.org,
	linuxppc-dev@lists.ozlabs.org, linux-embedded@vger.kernel.org
In-Reply-To: <4B5D560E.2020901@2net.co.uk>

2010/1/25 Chris Simmonds <chris@2net.co.uk>:
> Johnny Hung wrote:
>>
>> 2010/1/22 Marco Stornelli <marco.stornelli@gmail.com>:
>>>
>>> 2010/1/22 Johnny Hung <johnny.hacking@gmail.com>:
>>>>
>>>> 2010/1/20 Marco Stornelli <marco.stornelli@gmail.com>:
>>>>>
>>>>> 2010/1/20 Johnny Hung <johnny.hacking@gmail.com>:
>>>>>>
>>>>>> 2010/1/19 Matthias Kaehlcke <matthias@kaehlcke.net>:
>>>>>>>
>>>>>>> El Tue, Jan 19, 2010 at 02:17:22PM +0100 Ricard Wanderlof ha dit:
>>>>>>>
>>>>>> I consider to use ramdisk as rootfs because worry about wrong
>>>>>> operation in rootfs (is use jffs2 rootfs) and it will cause system
>>>>>> boot up failed.
>>>>>> Another query, does the syslogd/klogd log files also store in jffs2
>>>>>> rootfs? Write to jffs2 frequently will reduce flash life cycle.
>>>>>>
>>>>>> BRs, H. Johnny
>>>>>>>
>>>>>>> --
>>>>
>>>> It seems there are a lot of file-systems I have to study :P. The same
>>>> question is
>>>> how to split my rootfs? Re-mount /etc, /var to another file-sysyem mtd
>>>> part when
>>>> system boot up?
>>>>
>> Yes, I know. So if I want set etc directoyr to /dev/mtd5 not in rootfs
>> /, I need to add "/dev/mtdblock5  /etc        jffs2   defaults
>> 0       0" in /etc/fstab file but rootfs doesn't contain /etc
>> directory because /etc directoyr is store in /dev/mtdblock5.
>> Do you know what I mean? The kernel execute /sbin/init after mount
>> rootfs and /sbin/init is link to busybox, busybox will read
>> /etc/inittab file to initial. The problem is coming, how busybox to
>> read /etc in rootfs before mount /dev/mtdblock5 to /etc? There is no
>> program to mount /dev/mtdblock5 to /etc before busybox init execute.
>>
>> I think I must mistake some concept, please give me a hint.
>> Thank you
>> BRs, H. Johnny
>>
>

I got it. The rootfs contains at least /etc directory, /etc/inittab,
/etc/fstab. Bysybox will read /etc/fstab to mount etc to rootfs's etc
from other partition. Okay, it's reasonable. Another query, how to do
it in many Linux dist as Macro mentioned, use initramfs ? I am sure I
can arrange / to /dev/hda1 and /etc to /dev/hda2.

Thank you so much
BRs, H. Johnny

> You have two /etc directories: one in the the read-only root file system and
> one in the jffs2 fs. In the root fs you have /etc/fstab, /etc/inittab and
> any scripts it may call. The init program will mount /dev/mtdblock5 over the
> top of the /etc that is in the rootfs, so giving you the read/write version
> of /etc. Any files open in the old /etc - e.g. /etc/inittab - will continue
> to be open, but any new files opened in /etc will use the read/write version
> in jffs2. You can also do some interesting things with symbolic links...
> This technique works. I have used it in several projects.
>
> --
> Chris Simmonds                   2net Limited
> chris@2net.co.uk                 http://www.2net.co.uk/
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-embedded" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>

^ permalink raw reply

* Re: mount ramdisk rootfs /etc directory to jffs2 filesystem.
From: Chris Simmonds @ 2010-01-25  8:27 UTC (permalink / raw)
  To: Johnny Hung
  Cc: Marco Stornelli, Matthias Kaehlcke, Ricard Wanderlof,
	kernelnewbies, linux-mtd@lists.infradead.org,
	linuxppc-dev@lists.ozlabs.org, linux-embedded@vger.kernel.org
In-Reply-To: <cb9ecdfa1001242009x30eabcd3gbd0bb1d8a931199e@mail.gmail.com>

Johnny Hung wrote:
> 2010/1/22 Marco Stornelli <marco.stornelli@gmail.com>:
>> 2010/1/22 Johnny Hung <johnny.hacking@gmail.com>:
>>> 2010/1/20 Marco Stornelli <marco.stornelli@gmail.com>:
>>>> 2010/1/20 Johnny Hung <johnny.hacking@gmail.com>:
>>>>> 2010/1/19 Matthias Kaehlcke <matthias@kaehlcke.net>:
>>>>>> El Tue, Jan 19, 2010 at 02:17:22PM +0100 Ricard Wanderlof ha dit:
>>>>>>
>>>>> I consider to use ramdisk as rootfs because worry about wrong
>>>>> operation in rootfs (is use jffs2 rootfs) and it will cause system
>>>>> boot up failed.
>>>>> Another query, does the syslogd/klogd log files also store in jffs2
>>>>> rootfs? Write to jffs2 frequently will reduce flash life cycle.
>>>>>
>>>>> BRs, H. Johnny
>>>>>> --
>>> It seems there are a lot of file-systems I have to study :P. The same
>>> question is
>>> how to split my rootfs? Re-mount /etc, /var to another file-sysyem mtd part when
>>> system boot up?
>>>
> Yes, I know. So if I want set etc directoyr to /dev/mtd5 not in rootfs
> /, I need to add "/dev/mtdblock5  /etc        jffs2   defaults
> 0       0" in /etc/fstab file but rootfs doesn't contain /etc
> directory because /etc directoyr is store in /dev/mtdblock5.
> Do you know what I mean? The kernel execute /sbin/init after mount
> rootfs and /sbin/init is link to busybox, busybox will read
> /etc/inittab file to initial. The problem is coming, how busybox to
> read /etc in rootfs before mount /dev/mtdblock5 to /etc? There is no
> program to mount /dev/mtdblock5 to /etc before busybox init execute.
> 
> I think I must mistake some concept, please give me a hint.
> Thank you
> BRs, H. Johnny
> 

You have two /etc directories: one in the the read-only root file system 
and one in the jffs2 fs. In the root fs you have /etc/fstab, 
/etc/inittab and any scripts it may call. The init program will mount 
/dev/mtdblock5 over the top of the /etc that is in the rootfs, so giving 
you the read/write version of /etc. Any files open in the old /etc - 
e.g. /etc/inittab - will continue to be open, but any new files opened 
in /etc will use the read/write version in jffs2. You can also do some 
interesting things with symbolic links... This technique works. I have 
used it in several projects.

-- 
Chris Simmonds                   2net Limited
chris@2net.co.uk                 http://www.2net.co.uk/

^ permalink raw reply

* Re: mount ramdisk rootfs /etc directory to jffs2 filesystem.
From: Johnny Hung @ 2010-01-25  4:09 UTC (permalink / raw)
  To: Marco Stornelli
  Cc: Matthias Kaehlcke, Ricard Wanderlof, kernelnewbies,
	linux-mtd@lists.infradead.org, linuxppc-dev@lists.ozlabs.org,
	linux-embedded@vger.kernel.org
In-Reply-To: <2ea1731b1001220014v59ea7767m6e1a8547d41c6afb@mail.gmail.com>

2010/1/22 Marco Stornelli <marco.stornelli@gmail.com>:
> 2010/1/22 Johnny Hung <johnny.hacking@gmail.com>:
>> 2010/1/20 Marco Stornelli <marco.stornelli@gmail.com>:
>>> 2010/1/20 Johnny Hung <johnny.hacking@gmail.com>:
>>>> 2010/1/19 Matthias Kaehlcke <matthias@kaehlcke.net>:
>>>>> El Tue, Jan 19, 2010 at 02:17:22PM +0100 Ricard Wanderlof ha dit:
>>>>>
>>>> I consider to use ramdisk as rootfs because worry about wrong
>>>> operation in rootfs (is use jffs2 rootfs) and it will cause system
>>>> boot up failed.
>>>> Another query, does the syslogd/klogd log files also store in jffs2
>>>> rootfs? Write to jffs2 frequently will reduce flash life cycle.
>>>>
>>>> BRs, H. Johnny
>>>>>
>>>>> --
>>>
>>
>> It seems there are a lot of file-systems I have to study :P. The same
>> question is
>> how to split my rootfs? Re-mount /etc, /var to another file-sysyem mtd part when
>> system boot up?
>>
>
Yes, I know. So if I want set etc directoyr to /dev/mtd5 not in rootfs
/, I need to add "/dev/mtdblock5  /etc        jffs2   defaults
0       0" in /etc/fstab file but rootfs doesn't contain /etc
directory because /etc directoyr is store in /dev/mtdblock5.
Do you know what I mean? The kernel execute /sbin/init after mount
rootfs and /sbin/init is link to busybox, busybox will read
/etc/inittab file to initial. The problem is coming, how busybox to
read /etc in rootfs before mount /dev/mtdblock5 to /etc? There is no
program to mount /dev/mtdblock5 to /etc before busybox init execute.

I think I must mistake some concept, please give me a hint.
Thank you
BRs, H. Johnny

> Simply, you can mount each mount point with the fstab file and a
> script, same approach of every linux distribution, nothing more. Even
> in the pc world you can mount your /home on a partition with ext3,
> /var in a partition with ext4, and so on. A very simple approach to
> setup the system, it is to start with NFS for example with "whole" fs,
> copy what you need in the right place, setup the start-up script and
> reboot.
>
> Marco
>

^ permalink raw reply

* Re: mount ramdisk rootfs /etc directory to jffs2 filesystem.
From: Marco Stornelli @ 2010-01-22  8:14 UTC (permalink / raw)
  To: Johnny Hung
  Cc: Matthias Kaehlcke, Ricard Wanderlof, kernelnewbies,
	linux-mtd@lists.infradead.org, linuxppc-dev@lists.ozlabs.org,
	linux-embedded@vger.kernel.org
In-Reply-To: <cb9ecdfa1001211907u37415485tf3e1d2c443a506f7@mail.gmail.com>

2010/1/22 Johnny Hung <johnny.hacking@gmail.com>:
> 2010/1/20 Marco Stornelli <marco.stornelli@gmail.com>:
>> 2010/1/20 Johnny Hung <johnny.hacking@gmail.com>:
>>> 2010/1/19 Matthias Kaehlcke <matthias@kaehlcke.net>:
>>>> El Tue, Jan 19, 2010 at 02:17:22PM +0100 Ricard Wanderlof ha dit:
>>>>
>>> I consider to use ramdisk as rootfs because worry about wrong
>>> operation in rootfs (is use jffs2 rootfs) and it will cause system
>>> boot up failed.
>>> Another query, does the syslogd/klogd log files also store in jffs2
>>> rootfs? Write to jffs2 frequently will reduce flash life cycle.
>>>
>>> BRs, H. Johnny
>>>>
>>>> --
>>
>
> It seems there are a lot of file-systems I have to study :P. The same
> question is
> how to split my rootfs? Re-mount /etc, /var to another file-sysyem mtd part when
> system boot up?
>

Simply, you can mount each mount point with the fstab file and a
script, same approach of every linux distribution, nothing more. Even
in the pc world you can mount your /home on a partition with ext3,
/var in a partition with ext4, and so on. A very simple approach to
setup the system, it is to start with NFS for example with "whole" fs,
copy what you need in the right place, setup the start-up script and
reboot.

Marco

^ permalink raw reply

* Re: mount ramdisk rootfs /etc directory to jffs2 filesystem.
From: Johnny Hung @ 2010-01-22  3:07 UTC (permalink / raw)
  To: Marco Stornelli
  Cc: Matthias Kaehlcke, Ricard Wanderlof, kernelnewbies,
	linux-mtd@lists.infradead.org, linuxppc-dev@lists.ozlabs.org,
	linux-embedded@vger.kernel.org
In-Reply-To: <2ea1731b1001192357r72d627e2gb36d71f23fd69b2e@mail.gmail.com>

2010/1/20 Marco Stornelli <marco.stornelli@gmail.com>:
> 2010/1/20 Johnny Hung <johnny.hacking@gmail.com>:
>> 2010/1/19 Matthias Kaehlcke <matthias@kaehlcke.net>:
>>> El Tue, Jan 19, 2010 at 02:17:22PM +0100 Ricard Wanderlof ha dit:
>>>
>> I consider to use ramdisk as rootfs because worry about wrong
>> operation in rootfs (is use jffs2 rootfs) and it will cause system
>> boot up failed.
>> Another query, does the syslogd/klogd log files also store in jffs2
>> rootfs? Write to jffs2 frequently will reduce flash life cycle.
>>
>> BRs, H. Johnny
>>>
>>> --
>

It seems there are a lot of file-systems I have to study :P. The same
question is
how to split my rootfs? Re-mount /etc, /var to another file-sysyem mtd part when
system boot up?

Thank your good advice.
BRs, H. Johnny
> In general a good splitting for rootfs could be: squashfs for rootfs,
> tmpfs for volatile data (/tmp), ubifs (with a flash partition) for
> "strong" permanent data (/etc, ....) and pramfs for "light" permanent
> data (/var/log, .....).
> I think you should "split" your rootfs. Ramdisk is an old approach
> with some drawbacks.
>
> Marco
>

^ permalink raw reply

* Re: mount ramdisk rootfs /etc directory to jffs2 filesystem.
From: Johnny Hung @ 2010-01-22  3:01 UTC (permalink / raw)
  To: Ricard Wanderlof
  Cc: Matthias Kaehlcke, kernelnewbies, linux-mtd@lists.infradead.org,
	linuxppc-dev@lists.ozlabs.org, linux-embedded@vger.kernel.org
In-Reply-To: <Pine.LNX.4.64.1001200809550.32263@lnxricardw.se.axis.com>

2010/1/20 Ricard Wanderlof <ricard.wanderlof@axis.com>:
>
> On Wed, 20 Jan 2010, Johnny Hung wrote:
>
>>> i'd also recommend you to consider if you really need the
>>> ramdisk. when using a ram disk its entire content is loaded to the RAM
>>> occupying space, even if you don't use certain files (or part of
>>> them). other filesystems are more efficient in this aspect.
>>> if the main purpose is to have a read only rootfs, i'd suggest a look
>>> at squashfs.
>>
>> I consider to use ramdisk as rootfs because worry about wrong
>> operation in rootfs (is use jffs2 rootfs) and it will cause system
>> boot up failed.
>
> You have a point, however, you could do two things to help:
>
> a) Mount the root file system as read-only. That way you can never write to
> it, unless you remount it read-write. But you can still reflash that
> partition if you need to upgrade.
>
> b) Register the mtd partition holding the root file system as read-only.
> This is even more seecure as remounting the file system won't permit writes.
> However, it also means you can't write to it for upgrading. (I
> don't think the mtd core permits changing an already registered mtd
> partition from readonly to writable, but I could be wrong.)
>
>> Another query, does the syslogd/klogd log files also store in jffs2
>> rootfs? Write to jffs2 frequently will reduce flash life cycle.
>
> I think it is fairly common to have a combination of devices:
>
> / (root)        is a readonly flash device (mtd partition)
> /etc            is a writable flash device (mtd partition)
> /tmp and /var   are ramdisks (tmpfs), so they are writable, but lost when
>                power is cycled.

I think it's a good combination for my board. How do I sepcific the
/etc to another mtd part?
The busybox init will parse /etc/inittab after setup signal handler
and initializes console. I only
know to do it is to re-mount etc directory from another mtd to /etc.
Is it right?

Thank your help.
BRs, H. Johnny

>
> /Ricard
> --
> Ricard Wolf Wanderlöf                           ricardw(at)axis.com
> Axis Communications AB, Lund, Sweden            www.axis.com
> Phone +46 46 272 2016                           Fax +46 46 13 61 30
>

^ permalink raw reply

* Re: /dev/fw major not constant (embedded mdev devtmpfs ieee1394)
From: Greg KH @ 2010-01-21 21:26 UTC (permalink / raw)
  To: Mike Frysinger
  Cc: Paul Chavent, linux-embedded, linux-hotplug, linux1394-devel,
	busybox, libdc1394-devel
In-Reply-To: <8bd0f97a1001211321t4aea0ee0q8e0e253be2eff65a@mail.gmail.com>

On Thu, Jan 21, 2010 at 04:21:26PM -0500, Mike Frysinger wrote:
> On Thu, Jan 21, 2010 at 15:57, Greg KH wrote:
> > On Thu, Jan 21, 2010 at 09:56:10PM +0100, Paul Chavent wrote:
> >> I'am working on an embedded gnu/linux system that allow to capture an ieee1394 stream.
> >>
> >> The system is compound of a kernel, a basic initrd (libc,libgcc,busybox,libdc1394,...) and my app.
> >>
> >> I use the new firewire stack.
> >>
> >> As my system is very minimalist, i tought to create the nodes in /dev at the moment of building my initrd.
> >>
> >> But when i needed to modify my kernel configuration, it has changed the major number of /dev/fw*.
> >>
> >> It's not really important for the definitive system (it will be
> >> freeze), but i wonder how to solve this issue for my future works ?
> >>
> >> I don't need hotplug, i just need to fill /dev at startup (coldplug).
> >> The target is an embedded device, so it should be easy to
> >> build/configure/install, and lightweight.
> >>
> >> For those reasons i don't want to use udev.
> >
> > Why, is udev somehow not "lightweight"?
> 
> in comparison, it is absolutely not

Heh, in comparison to devtmpfs, yes, good point :)

thanks,

greg k-h

^ permalink raw reply

* Re: /dev/fw major not constant (embedded mdev devtmpfs ieee1394)
From: Mike Frysinger @ 2010-01-21 21:21 UTC (permalink / raw)
  To: Greg KH
  Cc: linux-embedded, busybox, libdc1394-devel, linux-hotplug,
	Paul Chavent, linux1394-devel
In-Reply-To: <20100121205711.GA29455@kroah.com>

On Thu, Jan 21, 2010 at 15:57, Greg KH wrote:
> On Thu, Jan 21, 2010 at 09:56:10PM +0100, Paul Chavent wrote:
>> I'am working on an embedded gnu/linux system that allow to capture an ieee1394 stream.
>>
>> The system is compound of a kernel, a basic initrd (libc,libgcc,busybox,libdc1394,...) and my app.
>>
>> I use the new firewire stack.
>>
>> As my system is very minimalist, i tought to create the nodes in /dev at the moment of building my initrd.
>>
>> But when i needed to modify my kernel configuration, it has changed the major number of /dev/fw*.
>>
>> It's not really important for the definitive system (it will be
>> freeze), but i wonder how to solve this issue for my future works ?
>>
>> I don't need hotplug, i just need to fill /dev at startup (coldplug).
>> The target is an embedded device, so it should be easy to
>> build/configure/install, and lightweight.
>>
>> For those reasons i don't want to use udev.
>
> Why, is udev somehow not "lightweight"?

in comparison, it is absolutely not
-mike

------------------------------------------------------------------------------
Throughout its 18-year history, RSA Conference consistently attracts the
world's best and brightest in the field, creating opportunities for Conference
attendees to learn about information security's most important issues through
interactions with peers, luminaries and emerging and established companies.
http://p.sf.net/sfu/rsaconf-dev2dev

^ permalink raw reply

* Re: /dev/fw major not constant (embedded mdev devtmpfs ieee1394)
From: Grant Likely @ 2010-01-21 20:59 UTC (permalink / raw)
  To: Paul Chavent
  Cc: linux-embedded, linux-hotplug, linux1394-devel, busybox,
	libdc1394-devel
In-Reply-To: <4B58BF6A.6070303@fnac.net>

On Thu, Jan 21, 2010 at 1:56 PM, Paul Chavent <paul.chavent@fnac.net> wrote:
> Hi.
>
> I'am working on an embedded gnu/linux system that allow to capture an
> ieee1394 stream.
[...]
> But when i needed to modify my kernel configuration, it has changed the
> major number of /dev/fw*.
[...]
> The linux ieee1394 wiki people give me some idea
> (http://marc.info/?t=126390679500003&r=1&w=2) :
>  - maintain a local patch for making the major constant
>  - use devtmpfs
>
> I prefer to try devtmpfs, but i would like to have your opinion on how would
> you do for me ?

devtmpfs is probably your best bet as that is what it is designed for.
 You can also run a trivial script at boot to read /proc/devices and
create the device nodes as described in chapter 3, page 47 of LDD3:

http://lwn.net/images/pdf/LDD3/ch03.pdf

g.

-- 
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.

^ permalink raw reply

* Re: /dev/fw major not constant (embedded mdev devtmpfs ieee1394)
From: Greg KH @ 2010-01-21 20:57 UTC (permalink / raw)
  To: Paul Chavent
  Cc: linux-embedded, linux-hotplug, linux1394-devel, busybox,
	libdc1394-devel
In-Reply-To: <4B58BF6A.6070303@fnac.net>

On Thu, Jan 21, 2010 at 09:56:10PM +0100, Paul Chavent wrote:
> Hi.
> 
> I'am working on an embedded gnu/linux system that allow to capture an ieee1394 stream.
> 
> The system is compound of a kernel, a basic initrd (libc,libgcc,busybox,libdc1394,...) and my app.
> 
> I use the new firewire stack.
> 
> As my system is very minimalist, i tought to create the nodes in /dev at the moment of building my initrd.
> 
> But when i needed to modify my kernel configuration, it has changed the major number of /dev/fw*.
> 
> It's not really important for the definitive system (it will be
> freeze), but i wonder how to solve this issue for my future works ?
> 
> I don't need hotplug, i just need to fill /dev at startup (coldplug).
> The target is an embedded device, so it should be easy to
> build/configure/install, and lightweight.
> 
> For those reasons i don't want to use udev.

Why, is udev somehow not "lightweight"?

> I tried mdev (i run mdev -s in my startup script) from busybox. The
> problem is that mdev only look in /sys/class for filling /dev (i have
> notified their mailing list
> http://lists.busybox.net/pipermail/busybox/2010-January/071384.html).
> 
> The linux ieee1394 wiki people give me some idea (http://marc.info/?t=126390679500003&r=1&w=2) :
>   - maintain a local patch for making the major constant
>   - use devtmpfs
> 
> I prefer to try devtmpfs, but i would like to have your opinion on how
> would you do for me ?

I would recommend using devtmpfs, this is exactly why it was created,
and is how lots of others are using it.  Why not try it out?

thanks,

greg k-h

^ permalink raw reply

* /dev/fw major not constant (embedded mdev devtmpfs ieee1394)
From: Paul Chavent @ 2010-01-21 20:56 UTC (permalink / raw)
  To: linux-embedded; +Cc: linux-hotplug, linux1394-devel, busybox, libdc1394-devel

Hi.

I'am working on an embedded gnu/linux system that allow to capture an ieee1394 stream.

The system is compound of a kernel, a basic initrd (libc,libgcc,busybox,libdc1394,...) and my app.

I use the new firewire stack.

As my system is very minimalist, i tought to create the nodes in /dev at the moment of building my initrd.

But when i needed to modify my kernel configuration, it has changed the major number of /dev/fw*.

It's not really important for the definitive system (it will be freeze), but i wonder how to solve this issue for my future works ?

I don't need hotplug, i just need to fill /dev at startup (coldplug). The target is an embedded device, so it should be easy to build/configure/install, and lightweight.

For those reasons i don't want to use udev.

I tried mdev (i run mdev -s in my startup script) from busybox. The problem is that mdev only look in /sys/class for filling /dev (i have notified their mailing list http://lists.busybox.net/pipermail/busybox/2010-January/071384.html).

The linux ieee1394 wiki people give me some idea (http://marc.info/?t=126390679500003&r=1&w=2) :
  - maintain a local patch for making the major constant
  - use devtmpfs

I prefer to try devtmpfs, but i would like to have your opinion on how would you do for me ?

Thank for your reading.

Paul.


^ permalink raw reply

* Re: mount ramdisk rootfs /etc directory to jffs2 filesystem.
From: Marco Stornelli @ 2010-01-20 11:54 UTC (permalink / raw)
  To: Matthias Kaehlcke
  Cc: Johnny Hung, Ricard Wanderlof, kernelnewbies,
	linux-mtd@lists.infradead.org, linuxppc-dev@lists.ozlabs.org,
	linux-embedded@vger.kernel.org
In-Reply-To: <20100120102145.GV16182@darwin>

2010/1/20 Matthias Kaehlcke <matthias@kaehlcke.net>:
> El Wed, Jan 20, 2010 at 08:57:44AM +0100 Marco Stornelli ha dit:
>
>> 2010/1/20 Johnny Hung <johnny.hacking@gmail.com>:
>> > 2010/1/19 Matthias Kaehlcke <matthias@kaehlcke.net>:
>> >> El Tue, Jan 19, 2010 at 02:17:22PM +0100 Ricard Wanderlof ha dit:
>> >>
>> > I consider to use ramdisk as rootfs because worry about wrong
>> > operation in rootfs (is use jffs2 rootfs) and it will cause system
>> > boot up failed.
>> > Another query, does the syslogd/klogd log files also store in jffs2
>> > rootfs? Write to jffs2 frequently will reduce flash life cycle.
>> >
>> > BRs, H. Johnny
>> >>
>> >> --
>>
>> In general a good splitting for rootfs could be: squashfs for rootfs,
>> tmpfs for volatile data (/tmp), ubifs (with a flash partition) for
>> "strong" permanent data (/etc, ....) and pramfs for "light" permanent
>> data (/var/log, .....).
>
> if ubifs is a good choice depends on the size of the partition, iirc
> it has a significant overhead for very small partitions.
>
> once using ubi it could be interesting to set up the read-only rootfs
> partition upon ubi in order to spread the wear out over a maximum of blocks.
>

I don't know the size constraints of Johnny, so it can be useful to use jffs2.

Marco

^ permalink raw reply

* Re: mount ramdisk rootfs /etc directory to jffs2 filesystem.
From: Matthias Kaehlcke @ 2010-01-20 10:21 UTC (permalink / raw)
  To: Marco Stornelli
  Cc: Johnny Hung, Ricard Wanderlof, kernelnewbies,
	linux-mtd@lists.infradead.org, linuxppc-dev@lists.ozlabs.org,
	linux-embedded@vger.kernel.org
In-Reply-To: <2ea1731b1001192357r72d627e2gb36d71f23fd69b2e@mail.gmail.com>

El Wed, Jan 20, 2010 at 08:57:44AM +0100 Marco Stornelli ha dit:

> 2010/1/20 Johnny Hung <johnny.hacking@gmail.com>:
> > 2010/1/19 Matthias Kaehlcke <matthias@kaehlcke.net>:
> >> El Tue, Jan 19, 2010 at 02:17:22PM +0100 Ricard Wanderlof ha dit:
> >>
> > I consider to use ramdisk as rootfs because worry about wrong
> > operation in rootfs (is use jffs2 rootfs) and it will cause system
> > boot up failed.
> > Another query, does the syslogd/klogd log files also store in jffs2
> > rootfs? Write to jffs2 frequently will reduce flash life cycle.
> >
> > BRs, H. Johnny
> >>
> >> --
> 
> In general a good splitting for rootfs could be: squashfs for rootfs,
> tmpfs for volatile data (/tmp), ubifs (with a flash partition) for
> "strong" permanent data (/etc, ....) and pramfs for "light" permanent
> data (/var/log, .....).

if ubifs is a good choice depends on the size of the partition, iirc
it has a significant overhead for very small partitions.

once using ubi it could be interesting to set up the read-only rootfs
partition upon ubi in order to spread the wear out over a maximum of blocks.

-- 
Matthias Kaehlcke
Embedded Linux Developer
Barcelona

            You can chain me, you can torture me, you can even
          destroy this body, but you will never imprison my mind
                            (Mahatma Gandhi)
                                                                 .''`.
    using free software / Debian GNU/Linux | http://debian.org  : :'  :
                                                                `. `'`
gpg --keyserver pgp.mit.edu --recv-keys 47D8E5D4                  `-

^ permalink raw reply

* Re: mount ramdisk rootfs /etc directory to jffs2 filesystem.
From: Marco Stornelli @ 2010-01-20  7:57 UTC (permalink / raw)
  To: Johnny Hung
  Cc: Matthias Kaehlcke, Ricard Wanderlof, kernelnewbies,
	linux-mtd@lists.infradead.org, linuxppc-dev@lists.ozlabs.org,
	linux-embedded@vger.kernel.org
In-Reply-To: <cb9ecdfa1001191832k28961740qa825a8c8d5e631f2@mail.gmail.com>

2010/1/20 Johnny Hung <johnny.hacking@gmail.com>:
> 2010/1/19 Matthias Kaehlcke <matthias@kaehlcke.net>:
>> El Tue, Jan 19, 2010 at 02:17:22PM +0100 Ricard Wanderlof ha dit:
>>
> I consider to use ramdisk as rootfs because worry about wrong
> operation in rootfs (is use jffs2 rootfs) and it will cause system
> boot up failed.
> Another query, does the syslogd/klogd log files also store in jffs2
> rootfs? Write to jffs2 frequently will reduce flash life cycle.
>
> BRs, H. Johnny
>>
>> --

In general a good splitting for rootfs could be: squashfs for rootfs,
tmpfs for volatile data (/tmp), ubifs (with a flash partition) for
"strong" permanent data (/etc, ....) and pramfs for "light" permanent
data (/var/log, .....).
I think you should "split" your rootfs. Ramdisk is an old approach
with some drawbacks.

Marco

^ permalink raw reply

* Re: mount ramdisk rootfs /etc directory to jffs2 filesystem.
From: Matthias Kaehlcke @ 2010-01-20  7:17 UTC (permalink / raw)
  To: Ricard Wanderlof
  Cc: Johnny Hung, kernelnewbies, linux-mtd@lists.infradead.org,
	linuxppc-dev@lists.ozlabs.org, linux-embedded@vger.kernel.org
In-Reply-To: <Pine.LNX.4.64.1001200809550.32263@lnxricardw.se.axis.com>

El Wed, Jan 20, 2010 at 08:15:01AM +0100 Ricard Wanderlof ha dit:

> On Wed, 20 Jan 2010, Johnny Hung wrote:
>
>>> i'd also recommend you to consider if you really need the
>>> ramdisk. when using a ram disk its entire content is loaded to the RAM
>>> occupying space, even if you don't use certain files (or part of
>>> them). other filesystems are more efficient in this aspect.
>>> if the main purpose is to have a read only rootfs, i'd suggest a look
>>> at squashfs.
>>
>> I consider to use ramdisk as rootfs because worry about wrong
>> operation in rootfs (is use jffs2 rootfs) and it will cause system
>> boot up failed.
>
> You have a point, however, you could do two things to help:
>
> a) Mount the root file system as read-only. That way you can never write  
> to it, unless you remount it read-write. But you can still reflash that  
> partition if you need to upgrade.
>
> b) Register the mtd partition holding the root file system as read-only.
> This is even more seecure as remounting the file system won't permit  
> writes. However, it also means you can't write to it for upgrading. (I
> don't think the mtd core permits changing an already registered mtd  
> partition from readonly to writable, but I could be wrong.)

AFAIK the mtd core doesn't permit changing a partition from ro to
rw. but if you happen to need to reflash the partition anyway, you can
load a tiny kernel modules that changes the flag indicating if a
partition is writable.

i once had to recurr to this solution and it works ;)

-- 
Matthias Kaehlcke
Embedded Linux Developer
Barcelona

    Tant qu'il y aura sur terre des hommes pour qui existe un concept
    d' 'honneur national', la menace d'une nouvelle guerre subsistera
                              (B. Traven)
                                                                 .''`.
    using free software / Debian GNU/Linux | http://debian.org  : :'  :
                                                                `. `'`
gpg --keyserver pgp.mit.edu --recv-keys 47D8E5D4                  `-

^ permalink raw reply

* Re: mount ramdisk rootfs /etc directory to jffs2 filesystem.
From: Ricard Wanderlof @ 2010-01-20  7:15 UTC (permalink / raw)
  To: Johnny Hung
  Cc: Ricard Wanderlöf, linux-embedded@vger.kernel.org,
	kernelnewbies, linux-mtd@lists.infradead.org, Matthias Kaehlcke,
	linuxppc-dev@lists.ozlabs.org
In-Reply-To: <cb9ecdfa1001191832k28961740qa825a8c8d5e631f2@mail.gmail.com>


On Wed, 20 Jan 2010, Johnny Hung wrote:

>> i'd also recommend you to consider if you really need the
>> ramdisk. when using a ram disk its entire content is loaded to the RAM
>> occupying space, even if you don't use certain files (or part of
>> them). other filesystems are more efficient in this aspect.
>> if the main purpose is to have a read only rootfs, i'd suggest a look
>> at squashfs.
>
> I consider to use ramdisk as rootfs because worry about wrong
> operation in rootfs (is use jffs2 rootfs) and it will cause system
> boot up failed.

You have a point, however, you could do two things to help:

a) Mount the root file system as read-only. That way you can never write 
to it, unless you remount it read-write. But you can still reflash that 
partition if you need to upgrade.

b) Register the mtd partition holding the root file system as read-only.
This is even more seecure as remounting the file system won't permit 
writes. However, it also means you can't write to it for upgrading. (I
don't think the mtd core permits changing an already registered mtd 
partition from readonly to writable, but I could be wrong.)

> Another query, does the syslogd/klogd log files also store in jffs2
> rootfs? Write to jffs2 frequently will reduce flash life cycle.

I think it is fairly common to have a combination of devices:

/ (root)        is a readonly flash device (mtd partition)
/etc            is a writable flash device (mtd partition)
/tmp and /var   are ramdisks (tmpfs), so they are writable, but lost when
                 power is cycled.

/Ricard
-- 
Ricard Wolf Wanderlöf                           ricardw(at)axis.com
Axis Communications AB, Lund, Sweden            www.axis.com
Phone +46 46 272 2016                           Fax +46 46 13 61 30

^ permalink raw reply

* Re: mount ramdisk rootfs /etc directory to jffs2 filesystem.
From: Matthias Kaehlcke @ 2010-01-20  7:12 UTC (permalink / raw)
  To: Johnny Hung
  Cc: Ricard Wanderlof, kernelnewbies, linux-mtd@lists.infradead.org,
	linuxppc-dev@lists.ozlabs.org, linux-embedded@vger.kernel.org
In-Reply-To: <cb9ecdfa1001191832k28961740qa825a8c8d5e631f2@mail.gmail.com>

El Wed, Jan 20, 2010 at 10:32:15AM +0800 Johnny Hung ha dit:

> 2010/1/19 Matthias Kaehlcke <matthias@kaehlcke.net>:
> > El Tue, Jan 19, 2010 at 02:17:22PM +0100 Ricard Wanderlof ha dit:
> >
> >> On Tue, 19 Jan 2010, Johnny Hung wrote:
> >>
> >>> Okay, I think the steps is below if my rootfs is ramdisk and configure
> >>> files in jffs2,
> >>>
> >>> 1. cp /etc/* /mnt/mtd/etc/    (/mnt/mtd is my jffs2 fs)
> >>> 2. rm -rf /etc/*
> >>> 3. make symbolic links from all /etc/xx to /mnt/mtd/etc/xxx
> >>> 4. remake ramdisk rootfs
> >>>
> >>> It seems all files in ramdisk rootfs /etc all links to /mnt/mtd/etc/
> >>> and try to modify these files is effective after reboot.
> >>> But is this a common way in embedded linux ?
> >>
> 
> Thanks, I understand.
> 
> >> In principle, but it is easier (and cleaner) to make a symbolic link from
> >> (say) /etc -> /mnt/mtd/etc without linking every individual file and
> >> directory.
> >
> > i totally agree with ricard when you want to move the entire directory
> > to jffs2 and not only some selected files
> >
> >> You could also use a jffs2 file system in flash for your rootfs, that way
> >> you wouldn't need a ramdisk at all.
> >
> > i'd also recommend you to consider if you really need the
> > ramdisk. when using a ram disk its entire content is loaded to the RAM
> > occupying space, even if you don't use certain files (or part of
> > them). other filesystems are more efficient in this aspect.
> > if the main purpose is to have a read only rootfs, i'd suggest a look
> > at squashfs.
> 
> I consider to use ramdisk as rootfs because worry about wrong
> operation in rootfs (is use jffs2 rootfs) and it will cause system
> boot up failed.

you could set up a rootfs partition with a read-only file system
(squashfs, jffs2 mounted ro) and a second partition that's writable.

> Another query, does the syslogd/klogd log files also store in jffs2
> rootfs? Write to jffs2 frequently will reduce flash life cycle.

by default the log files will be written to /var/log, if this
directory happens to be on a jffs2 partition writes will go there and
produce wear out. to avoid this you could set up a small tmpfs (in
RAM) and mount it on /var

-- 
Matthias Kaehlcke
Embedded Linux Developer
Barcelona

          We can't solve problems by using the same kind
             of thinking we used when we created them
                         (Albert Einstein)
                                                                 .''`.
    using free software / Debian GNU/Linux | http://debian.org  : :'  :
                                                                `. `'`
gpg --keyserver pgp.mit.edu --recv-keys 47D8E5D4                  `-

^ permalink raw reply

* Re: mount ramdisk rootfs /etc directory to jffs2 filesystem.
From: Johnny Hung @ 2010-01-20  2:32 UTC (permalink / raw)
  To: Matthias Kaehlcke
  Cc: Ricard Wanderlof, kernelnewbies, linux-mtd@lists.infradead.org,
	linuxppc-dev@lists.ozlabs.org, linux-embedded@vger.kernel.org
In-Reply-To: <20100119140600.GH16182@darwin>

2010/1/19 Matthias Kaehlcke <matthias@kaehlcke.net>:
> El Tue, Jan 19, 2010 at 02:17:22PM +0100 Ricard Wanderlof ha dit:
>
>> On Tue, 19 Jan 2010, Johnny Hung wrote:
>>
>>> Okay, I think the steps is below if my rootfs is ramdisk and configure
>>> files in jffs2,
>>>
>>> 1. cp /etc/* /mnt/mtd/etc/    (/mnt/mtd is my jffs2 fs)
>>> 2. rm -rf /etc/*
>>> 3. make symbolic links from all /etc/xx to /mnt/mtd/etc/xxx
>>> 4. remake ramdisk rootfs
>>>
>>> It seems all files in ramdisk rootfs /etc all links to /mnt/mtd/etc/
>>> and try to modify these files is effective after reboot.
>>> But is this a common way in embedded linux ?
>>

Thanks, I understand.

>> In principle, but it is easier (and cleaner) to make a symbolic link from
>> (say) /etc -> /mnt/mtd/etc without linking every individual file and
>> directory.
>
> i totally agree with ricard when you want to move the entire directory
> to jffs2 and not only some selected files
>
>> You could also use a jffs2 file system in flash for your rootfs, that way
>> you wouldn't need a ramdisk at all.
>
> i'd also recommend you to consider if you really need the
> ramdisk. when using a ram disk its entire content is loaded to the RAM
> occupying space, even if you don't use certain files (or part of
> them). other filesystems are more efficient in this aspect.
> if the main purpose is to have a read only rootfs, i'd suggest a look
> at squashfs.

I consider to use ramdisk as rootfs because worry about wrong
operation in rootfs (is use jffs2 rootfs) and it will cause system
boot up failed.
Another query, does the syslogd/klogd log files also store in jffs2
rootfs? Write to jffs2 frequently will reduce flash life cycle.

BRs, H. Johnny
>
> --
> Matthias Kaehlcke
> Embedded Linux Developer
> Barcelona
>
>              La posibilidad de realizar un suenyo es lo
>                 que hace que la vida sea interesante
>                                                                 .''`.
>    using free software / Debian GNU/Linux | http://debian.org  : :'  :
>                                                                `. `'`
> gpg --keyserver pgp.mit.edu --recv-keys 47D8E5D4                  `-
>

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox