* Re: [PWM v8 1/3] PWM: Implement a generic PWM framework
From: Mike Frysinger @ 2011-03-31 0:38 UTC (permalink / raw)
To: Bill Gatliff; +Cc: linux-kernel, linux-embedded
In-Reply-To: <AANLkTinfm-rCRvah1wKGHpPGHrFV3ne90xChmUVCYup9@mail.gmail.com>
On Wed, Mar 30, 2011 at 20:36, Mike Frysinger wrote:
> On Wed, Mar 30, 2011 at 20:33, Bill Gatliff wrote:
>> On Wed, Mar 30, 2011 at 6:57 PM, Mike Frysinger wrote:
>>> On Sat, Mar 12, 2011 at 23:24, Bill Gatliff wrote:
>>>> +T: git git://git.billgatliff.com/pwm.git
>>>
>>> $ git clone git://git.billgatliff.com/pwm.git
>>> Cloning into pwm...
>>> fatal: The remote end hung up unexpectedly
>>>
>>> :(
>>>
>>> why not get an account on kernel.org like everyone else and push your
>>> tree there ?
>>
>> Tried that once, got no response.
>>
>> I'm open to trying again if you can direct me to the right place.
>
> i used this:
> https://accounts.kernel.org/
>
> make sure to mention your PWM framework ?
in the mean time, you could easily use a free service like
http://repo.or.cz/ ...
-mike
^ permalink raw reply
* Re: [PWM v8 1/3] PWM: Implement a generic PWM framework
From: Bill Gatliff @ 2011-03-31 0:43 UTC (permalink / raw)
To: Mike Frysinger; +Cc: linux-kernel, linux-embedded
In-Reply-To: <AANLkTik1_Eekn7e0H+czCWHUX8LdioPH8MSHjcoZG6B=@mail.gmail.com>
Mike:
On Wed, Mar 30, 2011 at 6:57 PM, Mike Frysinger <vapier.adi@gmail.com> wrote:
> On Sat, Mar 12, 2011 at 23:24, Bill Gatliff wrote:
>> +T: git git://git.billgatliff.com/pwm.git
>
> $ git clone git://git.billgatliff.com/pwm.git
> Cloning into pwm...
> fatal: The remote end hung up unexpectedly
I just tried http://git.billgatliff.com/pwm.git, and that's working.
FWIW. I don't know why git:// has suddenly stopped working...
b.g.
--
Bill Gatliff
bgat@billgatliff.com
^ permalink raw reply
* Re: [PWM v8 1/3] PWM: Implement a generic PWM framework
From: Mike Frysinger @ 2011-03-31 0:49 UTC (permalink / raw)
To: Bill Gatliff; +Cc: linux-kernel, linux-embedded
In-Reply-To: <AANLkTi=fQsrVTo=dA03_75j_++Ya4-M1sd8T-9Lb1tPQ@mail.gmail.com>
On Wed, Mar 30, 2011 at 20:43, Bill Gatliff wrote:
> On Wed, Mar 30, 2011 at 6:57 PM, Mike Frysinger wrote:
>> On Sat, Mar 12, 2011 at 23:24, Bill Gatliff wrote:
>>> +T: git git://git.billgatliff.com/pwm.git
>>
>> $ git clone git://git.billgatliff.com/pwm.git
>> Cloning into pwm...
>> fatal: The remote end hung up unexpectedly
>
> I just tried http://git.billgatliff.com/pwm.git, and that's working.
> FWIW. I don't know why git:// has suddenly stopped working...
seems to be working, albeit with significantly more overhead. i guess
a one-off fetch is fine for now for me to play.
-mike
^ permalink raw reply
* Re: [PWM v8 1/3] PWM: Implement a generic PWM framework
From: Chris Ball @ 2011-03-31 2:36 UTC (permalink / raw)
To: Bill Gatliff; +Cc: Mike Frysinger, linux-kernel, linux-embedded
In-Reply-To: <AANLkTimEqRQS+yPpNuzF7XOk9cuDx2Mu1MKnfQkZDLfk@mail.gmail.com>
Hi Bill,
On Wed, Mar 30 2011, Bill Gatliff wrote:
>> why not get an account on kernel.org like everyone else and push your
>> tree there ?
>
> Tried that once, got no response.
>
> I'm open to trying again if you can direct me to the right place.
[cjb@hera ~]$ finger bgat
Login: bgat Name: Bill Gatliff
Directory: /home/bgat Shell: /bin/bash
Never logged in.
So, you have an account -- you probably missed the welcome e-mail with
login instructions. You should just get in touch with the admins and
ask for a resend of the welcome mail.
- Chris.
--
Chris Ball <cjb@laptop.org> <http://printf.net/>
One Laptop Per Child
^ permalink raw reply
* [PWM v9 0/3] Implement a generic PWM framework
From: Bill Gatliff @ 2011-04-01 3:59 UTC (permalink / raw)
To: linux-kernel, linux-embedded; +Cc: Bill Gatliff
This patch series contains the ninth attempt at implementation of a
generic PWM device interface framework. Think gpiolib, but for
devices and pseudo-devices that generate pulse-wave-modulated outputs.
Compared to the previous version, this patch series:
* Fixes an unbalanced get_device()/put_device()
A git tree containing these patches may be found here:
http://git.billgatliff.com/pwm.git
Regards,
b.g.
Bill Gatliff (3):
PWM: Implement a generic PWM framework
PWM: GPIO+hrtimer device emulation
PWM: Atmel PWMC driver
Documentation/pwm.txt | 276 ++++++++++++++++++++++
MAINTAINERS | 8 +
drivers/Kconfig | 2 +
drivers/Makefile | 2 +
drivers/pwm/Kconfig | 29 +++
drivers/pwm/Makefile | 7 +
drivers/pwm/atmel-pwmc.c | 452 ++++++++++++++++++++++++++++++++++++
drivers/pwm/gpio-pwm.c | 332 ++++++++++++++++++++++++++
drivers/pwm/pwm.c | 580 ++++++++++++++++++++++++++++++++++++++++++++++
include/linux/pwm/pwm.h | 143 ++++++++++++
10 files changed, 1831 insertions(+), 0 deletions(-)
create mode 100644 Documentation/pwm.txt
create mode 100644 drivers/pwm/Kconfig
create mode 100644 drivers/pwm/Makefile
create mode 100644 drivers/pwm/atmel-pwmc.c
create mode 100644 drivers/pwm/gpio-pwm.c
create mode 100644 drivers/pwm/pwm.c
create mode 100644 include/linux/pwm/pwm.h
--
1.7.4.1
^ permalink raw reply
* [PWM v9 1/3] PWM: Implement a generic PWM framework
From: Bill Gatliff @ 2011-04-01 3:59 UTC (permalink / raw)
To: linux-kernel, linux-embedded; +Cc: Bill Gatliff
In-Reply-To: <1301630392-20793-1-git-send-email-bgat@billgatliff.com>
Updates the existing PWM-related functions to support multiple
and/or hotplugged PWM devices, and adds a sysfs interface.
Moves the code to drivers/pwm.
For now, this new code can exist alongside the current PWM
implementations; the existing implementations will be migrated
to this new framework as time permits. Eventually, the current
PWM implementation will be deprecated and then expunged.
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
Documentation/pwm.txt | 276 ++++++++++++++++++++++
MAINTAINERS | 8 +
drivers/Kconfig | 2 +
drivers/Makefile | 2 +
drivers/pwm/Kconfig | 10 +
drivers/pwm/Makefile | 4 +
drivers/pwm/pwm.c | 580 +++++++++++++++++++++++++++++++++++++++++++++++
include/linux/pwm/pwm.h | 140 ++++++++++++
8 files changed, 1022 insertions(+), 0 deletions(-)
create mode 100644 Documentation/pwm.txt
create mode 100644 drivers/pwm/Kconfig
create mode 100644 drivers/pwm/Makefile
create mode 100644 drivers/pwm/pwm.c
create mode 100644 include/linux/pwm/pwm.h
diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt
new file mode 100644
index 0000000..6a0c95d
--- /dev/null
+++ b/Documentation/pwm.txt
@@ -0,0 +1,276 @@
+ Generic PWM Device API
+
+ February 7, 2011
+ 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 and pwm_config structures to invoke
+services in PWM peripheral device drivers. Consult
+drivers/pwm/atmel-pwmc.c for an example driver for the Atmel PWMC
+peripheral.
+
+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_ops structure, which describes
+the capabilities of the PWM hardware being utilized. 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_ops structure as users
+begin to configure and utilize the hardware.
+
+Many PWM-capable peripherals provide two, three, or more channels of
+PWM output. The driver author calls pwm_register() once for each
+channel they wish to be supported by the Generic PWM API.
+
+Note that PWM signals can be produced by a variety of peripherals,
+beyond the true PWM peripherals 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 be accommodated by the Generic PWM Device API framework.
+
+The following paragraphs describe the basic functions provided by the
+Generic PWM API framework. See the kerneldoc in drivers/pwm/pwm.c for
+the most detailed documentation.
+
+
+Using the API to Generate PWM Signals -- Basic Kernel Functions
+
+pwm_request() -- Returns a pwm_device 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_release() 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_release() -- 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 Sysfs Interface to Generate PWM Signals from Userspace
+
+The Generic PWM API provides the following attributes under
+/sys/class/pwm/<device>/ to allow user applications to control
+and/or monitor PWM signal generation. Except for the 'export'
+attribute, all attributes are read-only if the PWM device is not
+exported to userspace.
+
+export (rw) -- write a label to this attribute to request that the PWM
+device be exported to userspace; returns the length of the label on
+success (for compatibilty with echo/cat), or -EBUSY if the device is
+already in use by the kernel or has already been exported to
+userspace. Read from this attribute to obtain the label of the current
+PWM device owner, if any.
+
+unexport (w) -- write a non-null string to this attribute to release
+the PWM device; the device then becomes available for reexport and/or
+requests. Returns -EBUSY if the device is not currently exported,
+-EINVAL if the device is not currently in use, or the length of the
+string on success.
+
+polarity (rw) -- write an ascii '1' to set active high, or a '0' for
+active low. Read to obtain the current polarity.
+
+period_ns (rw) -- write an ascii decimal number to set the period of
+the PWM device, in nanoseconds. Value written must not be less than
+duty_ns or -EINVAL is returned. Read to determine the current period
+of the PWM device, which might be slightly different than the value
+requested due to hardware limitations.
+
+duty_ns (rw) -- write an ascii decimal number to set the duration of
+the active portion of the PWM period, in nanoseconds; value written
+must not exceed period_ns. Read to obtain current duty_ns, which may
+be slightly different than the value requested due to hardware
+limitations.
+
+tick_hz (r) -- indicates the base tick rate of the underlying
+hardware, in nanoseconds. Returns '0' if the rate is not yet known,
+which might be the case if the device has not been requested yet (some
+drivers don't initialize this value until the hardware is requested,
+because the value is dynamic).
+
+run (rw) -- write '1' to start PWM signal generation, '0' to stop.
+Read to determine whether the PWM device is running or not.
+
+
+Using the API to Generate PWM Signals -- Advanced Functions
+
+pwm_config() -- Passes a pwm_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 by the config structure.
+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_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, timer
+handlers, and other interrupt contexts, but must confine their
+configuration changes to only those that the driver can implement
+without sleeping. 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.
+
+
+Implementing a PWM Device API Driver -- Functions for Driver Authors
+
+
+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.
+
+release -- (optional) Invoked 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.
+
+config -- Invoked to change the device configuration, always from a
+sleep-compatible 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.
+
+
+FAQs and Additional Notes
+
+The Atmel PWMC pwm_config() function tries to satisfy the user's
+configuration request by first invoking pwm_config_nosleep(). If that
+operation fails, then the PWM peripheral is brought to a synchronized
+stop, the configuration changes are made, and the device is restarted.
+
+The Atmel PWMC's use of pwm_config_nosleep() from pwm_config()
+minimizes redundant code between the two functions, and relieves the
+pwm_config() function of the need to explicitly test whether a
+requested configuration change can be carried out while the PWM device
+is in its current mode.
+
+PWM API driver authors are encouraged to adopt the Atmel PWMC's
+pwm_config()-vs.-pwm_config_nosleep() strategy in implementations for
+other devices as well.
+
+
+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/MAINTAINERS b/MAINTAINERS
index 6b4b9cd..fe55958 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5051,6 +5051,14 @@ S: Maintained
F: Documentation/video4linux/README.pvrusb2
F: drivers/media/video/pvrusb2/
+PWM DEVICE API
+M: Bill Gatliff <bgat@billgatliff.com>
+L: linux-embedded@vger.kernel.org
+T: git git://git.billgatliff.com/pwm.git
+S: Maintained
+F: Documentation/pwm.txt
+F: drivers/pwm/
+
PXA2xx/PXA3xx SUPPORT
M: Eric Miao <eric.y.miao@gmail.com>
M: Russell King <linux@arm.linux.org.uk>
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 177c7d1..bc75da5 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -56,6 +56,8 @@ source "drivers/pps/Kconfig"
source "drivers/gpio/Kconfig"
+source "drivers/pwm/Kconfig"
+
source "drivers/w1/Kconfig"
source "drivers/power/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index 3f135b6..132a823 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -6,6 +6,8 @@
#
obj-y += gpio/
+obj-$(CONFIG_GENERIC_PWM) += pwm/
+
obj-$(CONFIG_PCI) += pci/
obj-$(CONFIG_PARISC) += parisc/
obj-$(CONFIG_RAPIDIO) += rapidio/
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
new file mode 100644
index 0000000..bc550f7
--- /dev/null
+++ b/drivers/pwm/Kconfig
@@ -0,0 +1,10 @@
+#
+# PWM infrastructure and devices
+#
+
+menuconfig GENERIC_PWM
+ tristate "PWM Support"
+ help
+ Enables PWM device support implemented via a generic
+ framework. If unsure, say N.
+
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
new file mode 100644
index 0000000..7baa201
--- /dev/null
+++ b/drivers/pwm/Makefile
@@ -0,0 +1,4 @@
+#
+# Makefile for pwm devices
+#
+obj-$(CONFIG_GENERIC_PWM) := pwm.o
diff --git a/drivers/pwm/pwm.c b/drivers/pwm/pwm.c
new file mode 100644
index 0000000..9a08fea
--- /dev/null
+++ b/drivers/pwm/pwm.c
@@ -0,0 +1,580 @@
+/*
+ * PWM API implementation
+ *
+ * Copyright (C) 2011 Bill Gatliff <bgat@billgatliff.com>
+ * Copyright (C) 2011 Arun Murthy <arun.murthy@stericsson.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/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/pwm/pwm.h>
+
+static const char *REQUEST_SYSFS = "sysfs";
+static struct class pwm_class;
+
+void pwm_set_drvdata(struct pwm_device *p, void *data)
+{
+ dev_set_drvdata(&p->dev, data);
+}
+EXPORT_SYMBOL(pwm_set_drvdata);
+
+void *pwm_get_drvdata(const struct pwm_device *p)
+{
+ return dev_get_drvdata(&p->dev);
+}
+EXPORT_SYMBOL(pwm_get_drvdata);
+
+static inline struct pwm_device *to_pwm_device(struct device *dev)
+{
+ return container_of(dev, struct pwm_device, dev);
+}
+
+static int pwm_match_name(struct device *dev, void *name)
+{
+ return !strcmp(name, dev_name(dev));
+}
+
+static int __pwm_request(struct pwm_device *p, const char *label)
+{
+ int ret;
+
+ if (!try_module_get(p->ops->owner))
+ return -ENODEV;
+
+ ret = test_and_set_bit(PWM_FLAG_REQUESTED, &p->flags);
+ if (ret) {
+ ret = -EBUSY;
+ goto err_flag_requested;
+ }
+
+ p->label = label;
+
+ if (p->ops->request) {
+ ret = p->ops->request(p);
+ if (ret)
+ goto err_request_ops;
+
+ }
+
+ return 0;
+
+err_request_ops:
+ clear_bit(PWM_FLAG_REQUESTED, &p->flags);
+
+err_flag_requested:
+ module_put(p->ops->owner);
+ return ret;
+}
+
+/**
+ * pwm_request - request a PWM device by name
+ *
+ * @name: name of PWM device
+ * @label: label that identifies requestor
+ *
+ * The @name format is driver-specific, but is typically of the form
+ * "<bus_id>:<chan>". For example, "atmel_pwmc:1" identifies the
+ * second ATMEL PWMC peripheral channel.
+ *
+ * Returns a pointer to the requested PWM device on success, -EINVAL
+ * otherwise.
+ */
+struct pwm_device *pwm_request(const char *name, const char *label)
+{
+ struct device *d;
+ struct pwm_device *p;
+ int ret;
+
+ d = class_find_device(&pwm_class, NULL, (char*)name, pwm_match_name);
+ if (!d)
+ return ERR_PTR(-EINVAL);
+
+ p = to_pwm_device(d);
+ ret = __pwm_request(p, label);
+ if (ret) {
+ put_device(d);
+ return ERR_PTR(ret);
+ }
+
+ return p;
+}
+EXPORT_SYMBOL(pwm_request);
+
+/**
+ * pwm_release - releases a previously-requested PWM channel
+ *
+ * @p: PWM device to release
+ */
+void pwm_release(struct pwm_device *p)
+{
+ if (!test_and_clear_bit(PWM_FLAG_REQUESTED, &p->flags)) {
+ WARN(1, "%s: releasing unrequested PWM device %s\n",
+ __func__, dev_name(&p->dev));
+ return;
+ }
+
+ pwm_stop(p);
+ pwm_unsynchronize(p, NULL);
+ p->label = NULL;
+
+ if (p->ops->release)
+ p->ops->release(p);
+
+ put_device(&p->dev);
+ module_put(p->ops->owner);
+}
+EXPORT_SYMBOL(pwm_release);
+
+static unsigned long pwm_ns_to_ticks(struct pwm_device *p, unsigned long nsecs)
+{
+ unsigned long long ticks;
+
+ ticks = nsecs;
+ ticks *= p->tick_hz;
+ do_div(ticks, 1000000000);
+ return ticks;
+}
+
+static unsigned long pwm_ticks_to_ns(struct pwm_device *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;
+}
+
+/**
+ * pwm_config_nosleep - configures a PWM device in an atomic context
+ *
+ * @p: PWM device to configure
+ * @c: configuration to apply to the PWM device
+ *
+ * Returns whatever the PWM device driver's config_nosleep() returns,
+ * or -ENOSYS if the PWM device driver does not have a
+ * config_nosleep() method.
+ */
+int pwm_config_nosleep(struct pwm_device *p, struct pwm_config *c)
+{
+ if (!p->ops->config_nosleep)
+ return -ENOSYS;
+
+ return p->ops->config_nosleep(p, c);
+}
+EXPORT_SYMBOL(pwm_config_nosleep);
+
+/**
+ * pwm_config - configures a PWM device
+ *
+ * @p: PWM device to configure
+ * @c: configuration to apply to the PWM device
+ *
+ * Performs some basic sanity checking of the parameters, and returns
+ * -EINVAL if they are found to be invalid. Otherwise, returns
+ * whatever the PWM device's config() method returns.
+ */
+int pwm_config(struct pwm_device *p, struct pwm_config *c)
+{
+ int ret = 0;
+
+ dev_dbg(&p->dev, "%s: config_mask %lu period_ticks %lu "
+ "duty_ticks %lu polarity %d\n",
+ __func__, c->config_mask, c->period_ticks,
+ c->duty_ticks, c->polarity);
+
+ switch (c->config_mask & (BIT(PWM_CONFIG_PERIOD_TICKS)
+ | BIT(PWM_CONFIG_DUTY_TICKS))) {
+ case BIT(PWM_CONFIG_PERIOD_TICKS):
+ if (p->duty_ticks > c->period_ticks)
+ ret = -EINVAL;
+ break;
+ case BIT(PWM_CONFIG_DUTY_TICKS):
+ if (p->period_ticks < c->duty_ticks)
+ ret = -EINVAL;
+ break;
+ case BIT(PWM_CONFIG_DUTY_TICKS) | BIT(PWM_CONFIG_PERIOD_TICKS):
+ if (c->duty_ticks > c->period_ticks)
+ ret = -EINVAL;
+ break;
+ default:
+ break;
+ }
+
+ if (ret)
+ return ret;
+ return p->ops->config(p, c);
+}
+EXPORT_SYMBOL(pwm_config);
+
+/**
+ * pwm_set - compatibility function to ease migration from older code
+ * @p: the PWM device to configure
+ * @period_ns: period of the desired PWM signal, in nanoseconds
+ * @duty_ns: duration of active portion of desired PWM signal, in nanoseconds
+ * @polarity: 1 if active period is high, zero otherwise
+ */
+int pwm_set(struct pwm_device *p, unsigned long period_ns,
+ unsigned long duty_ns, int polarity)
+{
+ struct pwm_config c = {
+ .config_mask = (BIT(PWM_CONFIG_PERIOD_TICKS)
+ | BIT(PWM_CONFIG_DUTY_TICKS)
+ | BIT(PWM_CONFIG_POLARITY)),
+ .period_ticks = pwm_ns_to_ticks(p, period_ns),
+ .duty_ticks = pwm_ns_to_ticks(p, duty_ns),
+ .polarity = polarity
+ };
+
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_set);
+
+int pwm_set_period_ns(struct pwm_device *p, unsigned long period_ns)
+{
+ struct pwm_config c = {
+ .config_mask = BIT(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_device *p)
+{
+ return pwm_ticks_to_ns(p, p->period_ticks);
+}
+EXPORT_SYMBOL(pwm_get_period_ns);
+
+int pwm_set_duty_ns(struct pwm_device *p, unsigned long duty_ns)
+{
+ struct pwm_config c = {
+ .config_mask = BIT(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_device *p)
+{
+ return pwm_ticks_to_ns(p, p->duty_ticks);
+}
+EXPORT_SYMBOL(pwm_get_duty_ns);
+
+int pwm_set_polarity(struct pwm_device *p, int polarity)
+{
+ struct pwm_config c = {
+ .config_mask = BIT(PWM_CONFIG_POLARITY),
+ .polarity = polarity,
+ };
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_set_polarity);
+
+int pwm_start(struct pwm_device *p)
+{
+ struct pwm_config c = {
+ .config_mask = BIT(PWM_CONFIG_START),
+ };
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_start);
+
+int pwm_stop(struct pwm_device *p)
+{
+ struct pwm_config c = {
+ .config_mask = BIT(PWM_CONFIG_STOP),
+ };
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_stop);
+
+int pwm_synchronize(struct pwm_device *p, struct pwm_device *to_p)
+{
+ if (!p->ops->synchronize)
+ return -ENOSYS;
+
+ return p->ops->synchronize(p, to_p);
+}
+EXPORT_SYMBOL(pwm_synchronize);
+
+int pwm_unsynchronize(struct pwm_device *p, struct pwm_device *from_p)
+{
+ if (!p->ops->unsynchronize)
+ return -ENOSYS;
+
+ return p->ops->unsynchronize(p, from_p);
+}
+EXPORT_SYMBOL(pwm_unsynchronize);
+
+static ssize_t pwm_run_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_device *p = to_pwm_device(dev);
+ return sprintf(buf, "%d\n", pwm_is_running(p));
+}
+
+static ssize_t pwm_run_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct pwm_device *p = to_pwm_device(dev);
+
+ if (!pwm_is_exported(p))
+ return -EPERM;
+
+ if (sysfs_streq(buf, "1"))
+ pwm_start(p);
+ else if (sysfs_streq(buf, "0"))
+ pwm_stop(p);
+ else
+ return -EINVAL;
+
+ return len;
+}
+
+static ssize_t pwm_tick_hz_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_device *p = to_pwm_device(dev);
+ return sprintf(buf, "%lu\n", p->tick_hz);
+}
+
+static ssize_t pwm_duty_ns_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_device *p = to_pwm_device(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_device *p = to_pwm_device(dev);
+ int ret;
+
+ if (!pwm_is_exported(p))
+ return -EPERM;
+
+ ret = strict_strtoul(buf, 10, &duty_ns);
+ if (ret)
+ return ret;
+ pwm_set_duty_ns(p, duty_ns);
+ return len;
+}
+
+static ssize_t pwm_period_ns_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_device *p = to_pwm_device(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_device *p = to_pwm_device(dev);
+ int ret;
+
+ if (!pwm_is_exported(p))
+ return -EPERM;
+
+ ret = strict_strtoul(buf, 10, &period_ns);
+ if (ret)
+ return ret;
+
+ pwm_set_period_ns(p, period_ns);
+ return len;
+}
+
+static ssize_t pwm_polarity_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_device *p = to_pwm_device(dev);
+ return sprintf(buf, "%d\n", p->polarity ? 1 : 0);
+}
+
+static ssize_t pwm_polarity_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ unsigned long polarity;
+ struct pwm_device *p = to_pwm_device(dev);
+ int ret;
+
+ if (!pwm_is_exported(p))
+ return -EPERM;
+
+ ret = strict_strtoul(buf, 10, &polarity);
+ if (ret)
+ return ret;
+
+ pwm_set_polarity(p, polarity);
+ return len;
+}
+
+static ssize_t pwm_export_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_device *p = to_pwm_device(dev);
+
+ if (pwm_is_requested(p))
+ return sprintf(buf, "%s\n", p->label);
+ return 0;
+}
+
+static ssize_t pwm_export_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct pwm_device *p = to_pwm_device(dev);
+ int ret;
+
+ get_device(dev);
+ ret = __pwm_request(p, REQUEST_SYSFS);
+
+ if (!ret)
+ set_bit(PWM_FLAG_EXPORTED, &p->flags);
+ else {
+ put_device(dev);
+ ret = -EBUSY;
+ }
+
+ return ret ? ret : len;
+}
+
+static ssize_t pwm_unexport_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct pwm_device *p = to_pwm_device(dev);
+
+ if (!pwm_is_exported(p))
+ return -EINVAL;
+
+ pwm_release(p);
+ clear_bit(PWM_FLAG_EXPORTED, &p->flags);
+ return len;
+}
+
+static struct device_attribute pwm_dev_attrs[] = {
+ __ATTR(export, S_IRUGO | S_IWUSR, pwm_export_show, pwm_export_store),
+ __ATTR(unexport, S_IWUSR, NULL, pwm_unexport_store),
+ __ATTR(polarity, S_IRUGO | S_IWUSR, pwm_polarity_show, pwm_polarity_store),
+ __ATTR(period_ns, S_IRUGO | S_IWUSR, pwm_period_ns_show, pwm_period_ns_store),
+ __ATTR(duty_ns, S_IRUGO | S_IWUSR, pwm_duty_ns_show, pwm_duty_ns_store),
+ __ATTR(tick_hz, S_IRUGO, pwm_tick_hz_show, NULL),
+ __ATTR(run, S_IRUGO | S_IWUSR, pwm_run_show, pwm_run_store),
+ __ATTR_NULL,
+};
+
+static struct class pwm_class = {
+ .name = "pwm",
+ .owner = THIS_MODULE,
+ .dev_attrs = pwm_dev_attrs,
+};
+
+static void __pwm_release(struct device *dev)
+{
+ struct pwm_device *p = container_of(dev, struct pwm_device, dev);
+ kfree(p);
+}
+
+/**
+ * pwm_register - registers a PWM device
+ *
+ * @ops: PWM device operations
+ * @parent: reference to parent device, if any
+ * @fmt: printf-style format specifier for device name
+ */
+struct pwm_device *pwm_register(const struct pwm_device_ops *ops,
+ struct device *parent, const char *fmt, ...)
+{
+ struct pwm_device *p;
+ int ret;
+ va_list vargs;
+
+ if (!ops || !ops->config)
+ return ERR_PTR(-EINVAL);
+
+ p = kzalloc(sizeof(*p), GFP_KERNEL);
+ if (!p)
+ return ERR_PTR(-ENOMEM);
+
+ p->ops = ops;
+
+ p->dev.class = &pwm_class;
+ p->dev.parent = parent;
+ p->dev.release = __pwm_release;
+
+ va_start(vargs, fmt);
+ ret = kobject_set_name_vargs(&p->dev.kobj, fmt, vargs);
+
+ ret = device_register(&p->dev);
+ if (ret)
+ goto err;
+
+ return p;
+
+err:
+ put_device(&p->dev);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(pwm_register);
+
+void pwm_unregister(struct pwm_device *p)
+{
+ device_unregister(&p->dev);
+}
+EXPORT_SYMBOL(pwm_unregister);
+
+static int __init pwm_init(void)
+{
+ return class_register(&pwm_class);
+}
+
+static void __exit pwm_exit(void)
+{
+ class_unregister(&pwm_class);
+}
+
+postcore_initcall(pwm_init);
+module_exit(pwm_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Bill Gatliff <bgat@billgatliff.com>");
+MODULE_DESCRIPTION("Generic PWM device API implementation");
diff --git a/include/linux/pwm/pwm.h b/include/linux/pwm/pwm.h
new file mode 100644
index 0000000..64707a7
--- /dev/null
+++ b/include/linux/pwm/pwm.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2011 Bill Gatliff <bgat@billgatliff.com>
+ * Copyright (C) 2011 Arun Murthy <arun.murth@stericsson.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
+ */
+#ifndef __LINUX_PWM_H
+#define __LINUX_PWM_H
+
+#include <linux/device.h>
+
+enum {
+ PWM_FLAG_REQUESTED = 0,
+ PWM_FLAG_STOP = 1,
+ PWM_FLAG_RUNNING = 2,
+ PWM_FLAG_EXPORTED = 3,
+};
+
+enum {
+ PWM_CONFIG_DUTY_TICKS = 0,
+ PWM_CONFIG_PERIOD_TICKS = 1,
+ PWM_CONFIG_POLARITY = 2,
+ PWM_CONFIG_START = 3,
+ PWM_CONFIG_STOP = 4,
+};
+
+struct pwm_config;
+struct pwm_device;
+
+struct pwm_device_ops {
+ struct module *owner;
+
+ int (*request) (struct pwm_device *p);
+ void (*release) (struct pwm_device *p);
+ int (*config) (struct pwm_device *p,
+ struct pwm_config *c);
+ int (*config_nosleep) (struct pwm_device *p,
+ struct pwm_config *c);
+ int (*synchronize) (struct pwm_device *p,
+ struct pwm_device *to_p);
+ int (*unsynchronize) (struct pwm_device *p,
+ struct pwm_device *from_p);
+};
+
+/**
+ * struct pwm_config - configuration data for a PWM device
+ *
+ * @config_mask: which fields are valid
+ * @duty_ticks: requested duty cycle, in ticks
+ * @period_ticks: requested period, in ticks
+ * @polarity: active high (1), or active low (0)
+ */
+struct pwm_config {
+ unsigned long config_mask;
+ unsigned long duty_ticks;
+ unsigned long period_ticks;
+ int polarity;
+};
+
+/**
+ * struct pwm_device - represents a PWM device
+ *
+ * @dev: device model reference
+ * @ops: operations supported by the PWM device
+ * @label: requestor of the PWM device, or NULL
+ * @flags: PWM device state, see FLAG_*
+ * @tick_hz: base tick rate of PWM device, in HZ
+ * @polarity: active high (1), or active low (0)
+ * @period_ticks: PWM device's current period, in ticks
+ * @duty_ticks: duration of PWM device's active cycle, in ticks
+ */
+struct pwm_device {
+ struct device dev;
+ const struct pwm_device_ops *ops;
+ const char *label;
+ unsigned long flags;
+ unsigned long tick_hz;
+ int polarity;
+ unsigned long period_ticks;
+ unsigned long duty_ticks;
+};
+
+struct pwm_device *pwm_request(const char *name, const char *label);
+void pwm_release(struct pwm_device *p);
+
+static inline int pwm_is_requested(const struct pwm_device *p)
+{
+ return test_bit(PWM_FLAG_REQUESTED, &p->flags);
+}
+
+static inline int pwm_is_running(const struct pwm_device *p)
+{
+ return test_bit(PWM_FLAG_RUNNING, &p->flags);
+}
+
+static inline int pwm_is_exported(const struct pwm_device *p)
+{
+ return test_bit(PWM_FLAG_EXPORTED, &p->flags);
+}
+
+struct pwm_device *pwm_register(const struct pwm_device_ops *ops, struct device *parent,
+ const char *fmt, ...);
+void pwm_unregister(struct pwm_device *p);
+
+void pwm_set_drvdata(struct pwm_device *p, void *data);
+void *pwm_get_drvdata(const struct pwm_device *p);
+
+int pwm_set(struct pwm_device *p, unsigned long period_ns,
+ unsigned long duty_ns, int polarity);
+
+int pwm_set_period_ns(struct pwm_device *p, unsigned long period_ns);
+unsigned long pwm_get_period_ns(struct pwm_device *p);
+
+int pwm_set_duty_ns(struct pwm_device *p, unsigned long duty_ns);
+unsigned long pwm_get_duty_ns(struct pwm_device *p);
+
+int pwm_set_polarity(struct pwm_device *p, int polarity);
+
+int pwm_start(struct pwm_device *p);
+int pwm_stop(struct pwm_device *p);
+
+int pwm_config_nosleep(struct pwm_device *p, struct pwm_config *c);
+int pwm_config(struct pwm_device *p, struct pwm_config *c);
+
+int pwm_synchronize(struct pwm_device *p, struct pwm_device *to_p);
+int pwm_unsynchronize(struct pwm_device *p, struct pwm_device *from_p);
+
+#endif
--
1.7.4.1
^ permalink raw reply related
* [PWM v9 2/3] PWM: GPIO+hrtimer device emulation
From: Bill Gatliff @ 2011-04-01 3:59 UTC (permalink / raw)
To: linux-kernel, linux-embedded; +Cc: Bill Gatliff
In-Reply-To: <1301630392-20793-1-git-send-email-bgat@billgatliff.com>
Emulates a PWM device using a GPIO pin and an hrtimer. Subject
to CPU, scheduler and hardware limitations, can support many
PWM outputs, e.g. as many as you have GPIO pins available for.
On a 200 MHz ARM9 processor, a PWM frequency of 100 Hz can be attained
with this code so long as the duty cycle remains between about 20-80%.
At higher or lower duty cycles, the transition events may arrive too
close for the scheduler and CPU to reliably service.
This driver supports creation of new GPIO+hrtimer PWM devices via
configfs:
# mount config /config -t configfs
# mkdir /config/gpio-pwm/<gpio number>
The new PWM device will appear as /sys/class/pwm/gpio-pwm.<gpio number>.
Caveats:
* The GPIO pin number must be valid, not already in use
* The output state of the GPIO pin is configured when the PWM starts
running i.e. not immediately upon request, because the polarity of
the inactive state of the pin isn't known until the pwm device's
'polarity' attribute is configured
* After creating and binding the pwm device, you must then request
it by writing to /sys/class/pwm/gpio-pwm.<gpio number>/export
Unbind and destroy the pwm device by first stopping and unexporting
the pwm device under sysfs as usual; then do:
# rm -rf /config/gpio-pwm/<gpio number>
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
drivers/pwm/Kconfig | 11 ++
drivers/pwm/Makefile | 2 +
drivers/pwm/gpio-pwm.c | 332 +++++++++++++++++++++++++++++++++++++++++++++++
include/linux/pwm/pwm.h | 3 +
4 files changed, 348 insertions(+), 0 deletions(-)
create mode 100644 drivers/pwm/gpio-pwm.c
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index bc550f7..476de67 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -8,3 +8,14 @@ menuconfig GENERIC_PWM
Enables PWM device support implemented via a generic
framework. If unsure, say N.
+config GPIO_PWM
+ tristate "GPIO+hrtimer PWM device emulation"
+ depends on GENERIC_PWM
+ help
+ When enabled, this feature emulates single-channel PWM
+ devices using high-resolution timers and GPIO pins. You may
+ create as many of these devices as desired, subject to CPU
+ throughput limitations and GPIO pin availability.
+
+ To compile this feature as a module, chose M here; the module
+ will be called gpio-pwm. If unsure, say N.
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 7baa201..ecec3e4 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -2,3 +2,5 @@
# Makefile for pwm devices
#
obj-$(CONFIG_GENERIC_PWM) := pwm.o
+
+obj-$(CONFIG_GPIO_PWM) += gpio-pwm.o
diff --git a/drivers/pwm/gpio-pwm.c b/drivers/pwm/gpio-pwm.c
new file mode 100644
index 0000000..b1a3801
--- /dev/null
+++ b/drivers/pwm/gpio-pwm.c
@@ -0,0 +1,332 @@
+/*
+ * Emulates a PWM device using an hrtimer and GPIO pin
+ *
+ * Copyright (C) 2011 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/workqueue.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <linux/completion.h>
+#include <linux/configfs.h>
+#include <linux/pwm/pwm.h>
+
+#define DRIVER_NAME KBUILD_MODNAME
+
+struct gpio_pwm {
+ struct pwm_device *pwm;
+ struct pwm_device_ops ops;
+ struct hrtimer t;
+ struct work_struct work;
+ spinlock_t lock;
+ struct completion complete;
+ int gpio;
+ int callback;
+ unsigned long polarity : 1;
+ unsigned long active : 1;
+};
+
+static void gpio_pwm_work(struct work_struct *work)
+{
+ struct gpio_pwm *gp = container_of(work, struct gpio_pwm, work);
+
+ gpio_direction_output(gp->gpio, !(!!gp->polarity ^ !!gp->active));
+}
+
+static enum hrtimer_restart gpio_pwm_timeout(struct hrtimer *t)
+{
+ struct gpio_pwm *gp = container_of(t, struct gpio_pwm, t);
+ struct pwm_device *p = gp->pwm;
+
+ if (unlikely(p->duty_ticks == 0))
+ gp->active = 0;
+ else if (unlikely(p->duty_ticks == p->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 (unlikely(!gp->active && test_bit(PWM_FLAG_STOP, &p->flags))) {
+ clear_bit(PWM_FLAG_STOP, &p->flags);
+ complete_all(&gp->complete);
+ goto done;
+ }
+
+ if (gp->active)
+ hrtimer_forward_now(&gp->t, ktime_set(0, p->duty_ticks));
+ else
+ hrtimer_forward_now(&gp->t, ktime_set(0, p->period_ticks
+ - p->duty_ticks));
+
+done:
+ return HRTIMER_RESTART;
+}
+
+static void gpio_pwm_start(struct pwm_device *p)
+{
+ struct gpio_pwm *gp = pwm_get_drvdata(p);
+
+ gp->active = 0;
+ hrtimer_start(&gp->t, ktime_set(0, p->period_ticks - p->duty_ticks),
+ HRTIMER_MODE_REL);
+ set_bit(PWM_FLAG_RUNNING, &p->flags);
+}
+
+static int gpio_pwm_config_nosleep(struct pwm_device *p, struct pwm_config *c)
+{
+ struct gpio_pwm *gp = pwm_get_drvdata(p);
+ int ret = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&gp->lock, flags);
+
+ switch (c->config_mask) {
+
+ case BIT(PWM_CONFIG_DUTY_TICKS):
+ p->duty_ticks = c->duty_ticks;
+ break;
+
+ case BIT(PWM_CONFIG_START):
+ if (!hrtimer_active(&gp->t)) {
+ gpio_pwm_start(p);
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ spin_unlock_irqrestore(&gp->lock, flags);
+ return ret;
+}
+
+static int gpio_pwm_stop_sync(struct pwm_device *p)
+{
+ struct gpio_pwm *gp = pwm_get_drvdata(p);
+ int ret;
+ int was_on = hrtimer_active(&gp->t);
+
+ if (was_on) {
+ do {
+ init_completion(&gp->complete);
+ set_bit(PWM_FLAG_STOP, &p->flags);
+ ret = wait_for_completion_interruptible(&gp->complete);
+ if (ret)
+ return ret;
+ } while (test_bit(PWM_FLAG_STOP, &p->flags));
+ }
+
+ clear_bit(PWM_FLAG_RUNNING, &p->flags);
+
+ return was_on;
+}
+
+static int gpio_pwm_config(struct pwm_device *p, struct pwm_config *c)
+{
+ struct gpio_pwm *gp = pwm_get_drvdata(p);
+ int was_on = 0;
+
+ if (!gpio_pwm_config_nosleep(p, c))
+ return 0;
+
+ might_sleep();
+
+ was_on = gpio_pwm_stop_sync(p);
+ if (was_on < 0)
+ return was_on;
+
+ if (test_bit(PWM_CONFIG_PERIOD_TICKS, &c->config_mask))
+ p->period_ticks = c->period_ticks;
+ if (test_bit(PWM_CONFIG_DUTY_TICKS, &c->config_mask))
+ p->duty_ticks = c->duty_ticks;
+ if (test_bit(PWM_CONFIG_POLARITY, &c->config_mask))
+ gp->polarity = !!c->polarity;
+
+ if (test_bit(PWM_CONFIG_START, &c->config_mask)
+ || (was_on && !test_bit(PWM_CONFIG_STOP, &c->config_mask)))
+ gpio_pwm_start(p);
+
+ return 0;
+}
+
+static int gpio_pwm_request(struct pwm_device *p)
+{
+ p->tick_hz = 1000000000UL;
+ return 0;
+}
+
+static const struct pwm_device_ops gpio_pwm_device_ops = {
+ .owner = THIS_MODULE,
+ .config = gpio_pwm_config,
+ .config_nosleep = gpio_pwm_config_nosleep,
+ .request = gpio_pwm_request,
+};
+
+struct pwm_device *gpio_pwm_create(int gpio)
+{
+ struct gpio_pwm *gp;
+ int ret = 0;
+
+ if (!gpio_is_valid(gpio))
+ return ERR_PTR(-EINVAL);
+
+ if (gpio_request(gpio, DRIVER_NAME))
+ return ERR_PTR(-EBUSY);
+
+ gp = kzalloc(sizeof(*gp), GFP_KERNEL);
+ if (!gp)
+ goto err_alloc;
+
+ gp->gpio = gpio;
+ INIT_WORK(&gp->work, gpio_pwm_work);
+ init_completion(&gp->complete);
+ hrtimer_init(&gp->t, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ gp->t.function = gpio_pwm_timeout;
+
+ gp->pwm = pwm_register(&gpio_pwm_device_ops, NULL, "%s:%d", DRIVER_NAME, gpio);
+ if (IS_ERR_OR_NULL(gp->pwm))
+ goto err_pwm_register;
+
+ pwm_set_drvdata(gp->pwm, gp);
+
+ return gp->pwm;
+
+err_pwm_register:
+ kfree(gp);
+err_alloc:
+ gpio_free(gpio);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(gpio_pwm_create);
+
+int gpio_pwm_destroy(struct pwm_device *p)
+{
+ struct gpio_pwm *gp = pwm_get_drvdata(p);
+
+ if (pwm_is_requested(gp->pwm)) {
+ if (pwm_is_running(gp->pwm))
+ pwm_stop(gp->pwm);
+ pwm_release(gp->pwm);
+ }
+ hrtimer_cancel(&gp->t);
+ cancel_work_sync(&gp->work);
+
+ pwm_unregister(gp->pwm);
+ gpio_free(gp->gpio);
+ kfree(gp);
+
+ return 0;
+}
+EXPORT_SYMBOL(gpio_pwm_destroy);
+
+#ifdef CONFIG_CONFIGFS_FS
+struct gpio_pwm_target {
+ struct config_item item;
+ struct pwm_device *p;
+};
+
+static struct config_item_type gpio_pwm_item_type = {
+ .ct_owner = THIS_MODULE,
+};
+
+static struct config_item *make_gpio_pwm_target(struct config_group *group,
+ const char *name)
+{
+ struct gpio_pwm_target *t;
+ unsigned long gpio;
+ int ret;
+
+ t = kzalloc(sizeof(*t), GFP_KERNEL);
+ if (!t)
+ return ERR_PTR(-ENOMEM);
+
+ ret = strict_strtoul(name, 10, &gpio);
+ if (ret || !gpio_is_valid(gpio)) {
+ ret = -EINVAL;
+ goto err_invalid_gpio;
+ }
+
+ config_item_init_type_name(&t->item, name, &gpio_pwm_item_type);
+
+ t->p = gpio_pwm_create(gpio);
+ if (IS_ERR_OR_NULL(t->p))
+ goto err_gpio_pwm_create;
+
+ return &t->item;
+
+err_gpio_pwm_create:
+err_invalid_gpio:
+ kfree(t);
+ return ERR_PTR(ret);
+}
+
+static void drop_gpio_pwm_target(struct config_group *group,
+ struct config_item *item)
+{
+ struct gpio_pwm_target *t =
+ container_of(item, struct gpio_pwm_target, item);
+
+ gpio_pwm_destroy(t->p);
+ config_item_put(&t->item);
+ kfree(t);
+}
+
+static struct configfs_group_operations gpio_pwm_subsys_group_ops = {
+ .make_item = make_gpio_pwm_target,
+ .drop_item = drop_gpio_pwm_target,
+};
+
+static struct config_item_type gpio_pwm_subsys_type = {
+ .ct_group_ops = &gpio_pwm_subsys_group_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+static struct configfs_subsystem gpio_pwm_subsys = {
+ .su_group = {
+ .cg_item = {
+ .ci_name = DRIVER_NAME,
+ .ci_type = &gpio_pwm_subsys_type,
+ },
+ },
+};
+
+static int __init gpio_pwm_init(void)
+{
+ config_group_init(&gpio_pwm_subsys.su_group);
+ mutex_init(&gpio_pwm_subsys.su_mutex);
+ return configfs_register_subsystem(&gpio_pwm_subsys);
+}
+module_init(gpio_pwm_init);
+
+static void __exit gpio_pwm_exit(void)
+{
+ configfs_unregister_subsystem(&gpio_pwm_subsys);
+}
+module_exit(gpio_pwm_exit);
+#endif
+
+MODULE_AUTHOR("Bill Gatliff <bgat@billgatliff.com>");
+MODULE_DESCRIPTION("PWM channel emulator using GPIO and a high-resolution timer");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/pwm/pwm.h b/include/linux/pwm/pwm.h
index 64707a7..4fac560 100644
--- a/include/linux/pwm/pwm.h
+++ b/include/linux/pwm/pwm.h
@@ -137,4 +137,7 @@ int pwm_config(struct pwm_device *p, struct pwm_config *c);
int pwm_synchronize(struct pwm_device *p, struct pwm_device *to_p);
int pwm_unsynchronize(struct pwm_device *p, struct pwm_device *from_p);
+struct pwm_device *gpio_pwm_create(int gpio);
+int gpio_pwm_destroy(struct pwm_device *p);
+
#endif
--
1.7.4.1
^ permalink raw reply related
* [PWM v9 3/3] PWM: Atmel PWMC driver
From: Bill Gatliff @ 2011-04-01 3:59 UTC (permalink / raw)
To: linux-kernel, linux-embedded; +Cc: Bill Gatliff
In-Reply-To: <1301630392-20793-1-git-send-email-bgat@billgatliff.com>
Driver to allow the Atmel PWMC peripheral found on various
AT91 SoCs to be controlled using the Generic PWM framework.
Tested on the AT91SAM9263.
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
drivers/pwm/Kconfig | 8 +
drivers/pwm/Makefile | 1 +
drivers/pwm/atmel-pwmc.c | 452 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 461 insertions(+), 0 deletions(-)
create mode 100644 drivers/pwm/atmel-pwmc.c
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 476de67..560324a 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -19,3 +19,11 @@ config GPIO_PWM
To compile this feature as a module, chose M here; the module
will be called gpio-pwm. If unsure, say N.
+
+config ATMEL_PWMC
+ tristate "Atmel AT32/AT91 PWMC support"
+ depends on GENERIC_PWM && (AVR32 || ARCH_AT91SAM9263 || ARCH_AT91SAM9RL || ARCH_AT91CAP9)
+ help
+ This option enables support under the generic PWM
+ framework for PWMC peripheral channels found on
+ certain Atmel microcontrollers. If unsure, say N.
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index ecec3e4..d274fa0 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -4,3 +4,4 @@
obj-$(CONFIG_GENERIC_PWM) := pwm.o
obj-$(CONFIG_GPIO_PWM) += gpio-pwm.o
+obj-$(CONFIG_ATMEL_PWMC) += atmel-pwmc.o
diff --git a/drivers/pwm/atmel-pwmc.c b/drivers/pwm/atmel-pwmc.c
new file mode 100644
index 0000000..1339335
--- /dev/null
+++ b/drivers/pwm/atmel-pwmc.c
@@ -0,0 +1,452 @@
+/*
+ * Atmel PWMC peripheral driver
+ *
+ * Copyright (C) 2011 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/slab.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/completion.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,
+};
+
+/* TODO: NCHAN==4 only for certain AT91-ish parts! */
+#define NCHAN 4
+struct atmel_pwmc {
+ struct pwm_device *p[NCHAN];
+ struct pwm_device_ops ops;
+ spinlock_t lock;
+ struct completion complete;
+ void __iomem *iobase;
+ struct clk *clk;
+ u32 ccnt_mask;
+};
+
+/* TODO: debugfs attributes for peripheral register values */
+
+static inline void pwmc_writel(const struct atmel_pwmc *p, unsigned offset, u32 val)
+{
+ __raw_writel(val, p->iobase + offset);
+}
+
+static inline u32 pwmc_readl(const struct atmel_pwmc *p, unsigned offset)
+{
+ return __raw_readl(p->iobase + offset);
+}
+
+static int __to_chan(const struct atmel_pwmc *ap, const struct pwm_device *p)
+{
+ int chan;
+ for (chan = 0; chan < NCHAN; chan++)
+ if (p == ap->p[chan])
+ return chan;
+ BUG();
+ return 0;
+}
+
+
+static void pwmc_chan_writel(const struct atmel_pwmc *ap, int chan, int offset, int val)
+{
+ 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
+ + (chan * PWMC_CHAN_STRIDE), val);
+}
+
+static u32 pwmc_chan_readl(const struct atmel_pwmc *ap, int chan, int offset)
+{
+ return pwmc_readl(ap, offset + PWMC_CHAN_BASE
+ + (chan * PWMC_CHAN_STRIDE));
+}
+
+static int __atmel_pwmc_is_on(const struct atmel_pwmc *ap, int chan)
+{
+ return (pwmc_readl(ap, PWMC_SR) & BIT(chan)) ? 1 : 0;
+}
+
+static void __atmel_pwmc_stop(const struct atmel_pwmc *ap, int chan)
+{
+ pwmc_writel(ap, PWMC_DIS, BIT(chan));
+}
+
+static void __atmel_pwmc_start(const struct atmel_pwmc *ap, int chan)
+{
+ pwmc_writel(ap, PWMC_ENA, BIT(chan));
+}
+
+static void __atmel_pwmc_config_polarity(struct atmel_pwmc *ap, int chan,
+ struct pwm_config *c)
+{
+ unsigned long cmr = pwmc_chan_readl(ap, chan, PWMC_CMR);
+ struct pwm_device *p = ap->p[chan];
+
+ if (c->polarity)
+ clear_bit(PWMC_CMR_CPOL, &cmr);
+ else
+ set_bit(PWMC_CMR_CPOL, &cmr);
+ pwmc_chan_writel(ap, chan, PWMC_CMR, cmr);
+ p->polarity = c->polarity ? 1 : 0;
+
+ dev_dbg(&p->dev, "polarity %d\n", c->polarity);
+}
+
+static void __atmel_pwmc_config_duty_ticks(struct atmel_pwmc *ap, int chan,
+ struct pwm_config *c)
+{
+ unsigned long cmr, cprd, cpre, cdty;
+ struct pwm_device *p = ap->p[chan];
+
+ cmr = pwmc_chan_readl(ap, chan, PWMC_CMR);
+ cprd = pwmc_chan_readl(ap, chan, PWMC_CPRD);
+
+ cpre = cmr & PWMC_CMR_CPRE_MASK;
+ clear_bit(PWMC_CMR_CPD, &cmr);
+
+ cdty = cprd - (c->duty_ticks >> cpre);
+
+ p->duty_ticks = c->duty_ticks;
+
+ if (__atmel_pwmc_is_on(ap, chan)) {
+ pwmc_chan_writel(ap, chan, PWMC_CMR, cmr);
+ pwmc_chan_writel(ap, chan, PWMC_CUPD, cdty);
+ } else
+ pwmc_chan_writel(ap, chan, PWMC_CDTY, cdty);
+
+ dev_dbg(&p->dev, "duty_ticks = %lu cprd = %lx"
+ " cdty = %lx cpre = %lx\n", p->duty_ticks,
+ cprd, cdty, cpre);
+}
+
+static int __atmel_pwmc_config_period_ticks(struct atmel_pwmc *ap, int chan,
+ struct pwm_config *c)
+{
+ u32 cmr, cprd, cpre;
+ struct pwm_device *p = ap->p[chan];
+
+ cpre = fls(c->period_ticks);
+ if (cpre < 16)
+ cpre = 0;
+ else {
+ cpre -= 15;
+ if (cpre > 10)
+ return -EINVAL;
+ }
+
+ cmr = pwmc_chan_readl(ap, chan, PWMC_CMR);
+ cmr &= ~PWMC_CMR_CPRE_MASK;
+ cmr |= cpre;
+
+ cprd = c->period_ticks >> cpre;
+
+ pwmc_chan_writel(ap, chan, PWMC_CMR, cmr);
+ pwmc_chan_writel(ap, chan, PWMC_CPRD, cprd);
+ p->period_ticks = c->period_ticks;
+
+ dev_dbg(&p->dev, "period_ticks = %lu cprd = %x cpre = %x\n",
+ p->period_ticks, cprd, cpre);
+ return 0;
+}
+
+static int atmel_pwmc_config_nosleep(struct pwm_device *p, struct pwm_config *c)
+{
+ struct atmel_pwmc *ap = pwm_get_drvdata(p);
+ int chan = __to_chan(ap, p);
+ int ret = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ap->lock, flags);
+
+ switch (c->config_mask) {
+
+ case BIT(PWM_CONFIG_DUTY_TICKS):
+ __atmel_pwmc_config_duty_ticks(ap, chan, c);
+ break;
+
+ case BIT(PWM_CONFIG_STOP):
+ __atmel_pwmc_stop(ap, chan);
+ break;
+
+ case BIT(PWM_CONFIG_START):
+ __atmel_pwmc_start(ap, chan);
+ break;
+
+ case BIT(PWM_CONFIG_POLARITY):
+ __atmel_pwmc_config_polarity(ap, chan, c);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ spin_unlock_irqrestore(&ap->lock, flags);
+ return ret;
+}
+
+static int atmel_pwmc_stop_sync(struct atmel_pwmc *ap, int chan)
+{
+ struct pwm_device *p = ap->p[chan];
+ int was_on = __atmel_pwmc_is_on(ap, chan);
+ int ret;
+
+ if (!was_on)
+ return 0;
+
+ do {
+ init_completion(&ap->complete);
+ set_bit(PWM_FLAG_STOP, &p->flags);
+ pwmc_writel(ap, PWMC_IER, BIT(chan));
+
+ dev_dbg(&p->dev, "waiting on stop_sync completion...\n");
+
+ ret = wait_for_completion_interruptible(&ap->complete);
+
+ dev_dbg(&p->dev, "stop_sync complete (%d)\n", ret);
+
+ if (ret)
+ return ret;
+ } while (test_bit(PWM_FLAG_STOP, &p->flags));
+
+ return 1;
+}
+
+static int atmel_pwmc_config(struct pwm_device *p, struct pwm_config *c)
+{
+ struct atmel_pwmc *ap = pwm_get_drvdata(p);
+ int chan = __to_chan(ap, p);
+ int was_on = 0;
+ int ret;
+
+
+ if (!atmel_pwmc_config_nosleep(p, c))
+ return 0;
+
+ might_sleep();
+
+ dev_dbg(&p->dev, "config_mask %lx\n", c->config_mask);
+
+ was_on = atmel_pwmc_stop_sync(ap, chan);
+ if (was_on < 0)
+ return was_on;
+
+ if (test_bit(PWM_CONFIG_PERIOD_TICKS, &c->config_mask)) {
+ ret = __atmel_pwmc_config_period_ticks(ap, chan, c);
+ if (ret)
+ return ret;
+
+ if (!test_bit(PWM_CONFIG_DUTY_TICKS, &c->config_mask)) {
+ struct pwm_config d = {
+ .config_mask = PWM_CONFIG_DUTY_TICKS,
+ .duty_ticks = p->duty_ticks,
+ };
+ __atmel_pwmc_config_duty_ticks(ap, chan, &d);
+ }
+ }
+
+ if (test_bit(PWM_CONFIG_DUTY_TICKS, &c->config_mask))
+ __atmel_pwmc_config_duty_ticks(ap, chan, c);
+
+ if (test_bit(PWM_CONFIG_POLARITY, &c->config_mask))
+ __atmel_pwmc_config_polarity(ap, chan, c);
+
+ if (test_bit(PWM_CONFIG_START, &c->config_mask)
+ || (was_on && !test_bit(PWM_CONFIG_STOP, &c->config_mask)))
+ __atmel_pwmc_start(ap, chan);
+
+ return 0;
+}
+
+static int atmel_pwmc_request(struct pwm_device *p)
+{
+ struct atmel_pwmc *ap = pwm_get_drvdata(p);
+ int chan = __to_chan(ap, p);
+ unsigned long flags;
+
+ spin_lock_irqsave(&ap->lock, flags);
+ clk_enable(ap->clk);
+ p->tick_hz = clk_get_rate(ap->clk);
+ __atmel_pwmc_stop(ap, chan);
+ spin_unlock_irqrestore(&ap->lock, flags);
+
+ return 0;
+}
+
+static void atmel_pwmc_release(struct pwm_device *p)
+{
+ struct atmel_pwmc *ap = pwm_get_drvdata(p);
+ clk_disable(ap->clk);
+}
+
+const struct pwm_device_ops atmel_pwm_ops = {
+ .request = atmel_pwmc_request,
+ .release = atmel_pwmc_release,
+ .config_nosleep = atmel_pwmc_config_nosleep,
+ .config = atmel_pwmc_config,
+ .owner = THIS_MODULE,
+};
+
+static int __devinit atmel_pwmc_probe(struct platform_device *pdev)
+{
+ struct atmel_pwmc *ap;
+ struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ signed int chan;
+ int ret = 0;
+
+ ap = kzalloc(sizeof(*ap), GFP_KERNEL);
+ if (!ap) {
+ ret = -ENOMEM;
+ goto err_atmel_pwmc_alloc;
+ }
+
+ spin_lock_init(&ap->lock);
+ init_completion(&ap->complete);
+ platform_set_drvdata(pdev, ap);
+
+ /* TODO: the datasheets are unclear as to how large CCNT
+ * actually is across all adopters of the PWMC; sixteen bits
+ * seems a safe assumption for now */
+ ap->ccnt_mask = 0xffffUL;
+
+ 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, resource_size(r));
+ 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);
+
+ for (chan = 0; chan < NCHAN; chan++) {
+ ap->p[chan] = pwm_register(&atmel_pwm_ops, &pdev->dev, "%s:%d",
+ dev_name(&pdev->dev), chan);
+ if (IS_ERR_OR_NULL(ap->p[chan]))
+ goto err_pwm_register;
+ pwm_set_drvdata(ap->p[chan], ap);
+ }
+
+ return 0;
+
+err_pwm_register:
+ while (--chan > 0)
+ pwm_unregister(ap->p[chan]);
+
+ iounmap(ap->iobase);
+err_ioremap:
+ clk_put(ap->clk);
+err_clk_get:
+ platform_set_drvdata(pdev, NULL);
+ kfree(ap);
+err_atmel_pwmc_alloc:
+ dev_dbg(&pdev->dev, "%s: error, returning %d\n", __func__, ret);
+ return ret;
+}
+
+static int __devexit atmel_pwmc_remove(struct platform_device *pdev)
+{
+ struct atmel_pwmc *ap = platform_get_drvdata(pdev);
+ int chan;
+
+ for (chan = 0; chan < NCHAN; chan++)
+ pwm_unregister(ap->p[chan]);
+
+ clk_enable(ap->clk);
+ pwmc_writel(ap, PWMC_IDR, -1);
+ pwmc_writel(ap, PWMC_DIS, -1);
+ clk_disable(ap->clk);
+
+ clk_put(ap->clk);
+ iounmap(ap->iobase);
+
+ kfree(ap);
+
+ return 0;
+}
+
+static struct platform_driver atmel_pwmc_driver = {
+ .driver = {
+ /* note: this name has to match the one in at91*_devices.c */
+ .name = "atmel_pwmc",
+ .owner = THIS_MODULE,
+ },
+ .probe = atmel_pwmc_probe,
+ .remove = __devexit_p(atmel_pwmc_remove),
+};
+
+static int __init atmel_pwmc_init(void)
+{
+ return platform_driver_register(&atmel_pwmc_driver);
+}
+module_init(atmel_pwmc_init);
+
+static void __exit atmel_pwmc_exit(void)
+{
+ platform_driver_unregister(&atmel_pwmc_driver);
+}
+module_exit(atmel_pwmc_exit);
+
+MODULE_AUTHOR("Bill Gatliff <bgat@billgatliff.com>");
+MODULE_DESCRIPTION("Driver for Atmel PWMC peripheral");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:atmel_pwmc");
--
1.7.4.1
^ permalink raw reply related
* Re: [PWM v8 1/3] PWM: Implement a generic PWM framework
From: Bill Gatliff @ 2011-04-02 23:09 UTC (permalink / raw)
To: Mike Frysinger; +Cc: linux-kernel, linux-embedded
In-Reply-To: <AANLkTi=o3TCBOhd8gV5=2TOkL1eTrwXJqArLy7PLTkGC@mail.gmail.com>
Guys:
On Wed, Mar 30, 2011 at 7:49 PM, Mike Frysinger <vapier.adi@gmail.com> wrote:
> On Wed, Mar 30, 2011 at 20:43, Bill Gatliff wrote:
>> On Wed, Mar 30, 2011 at 6:57 PM, Mike Frysinger wrote:
>>> On Sat, Mar 12, 2011 at 23:24, Bill Gatliff wrote:
>>>> +T: git git://git.billgatliff.com/pwm.git
>>>
>>> $ git clone git://git.billgatliff.com/pwm.git
>>> Cloning into pwm...
>>> fatal: The remote end hung up unexpectedly
>>
>> I just tried http://git.billgatliff.com/pwm.git, and that's working.
>> FWIW. I don't know why git:// has suddenly stopped working...
>
> seems to be working, albeit with significantly more overhead. i guess
> a one-off fetch is fine for now for me to play.
If anyone cares, git://git.billgatliff.com/pwm.git is back up.
b.g.
--
Bill Gatliff
bgat@billgatliff.com
^ permalink raw reply
* [PATCH] ssb: Use pr_fmt and pr_<level>, remove CONFIG_SSB_SILENT
From: Joe Perches @ 2011-04-05 22:30 UTC (permalink / raw)
To: Michael Büsch; +Cc: netdev, LKML, linux-embedded
In-Reply-To: <1302035106.1923.7.camel@maggie>
Use the more common logging styles.
Remove CONFIG_SSB_SILENT which doesn't appear particularly useful.
Remove "ERROR: " prefixes from pr_err messages.
Signed-off-by: Joe Perches <joe@perches.com>
---
drivers/ssb/Kconfig | 14 +---------
drivers/ssb/driver_chipcommon.c | 2 +-
drivers/ssb/driver_chipcommon_pmu.c | 27 +++++++++---------
drivers/ssb/driver_mipscore.c | 22 ++++++++-------
drivers/ssb/driver_pcicore.c | 15 +++++-----
drivers/ssb/main.c | 48 +++++++++++++++-----------------
drivers/ssb/pci.c | 49 +++++++++++++++-----------------
drivers/ssb/pcmcia.c | 52 +++++++++++++++--------------------
drivers/ssb/scan.c | 36 ++++++++++--------------
drivers/ssb/sprom.c | 6 +++-
drivers/ssb/ssb_private.h | 10 +------
include/linux/ssb/ssb.h | 4 +-
12 files changed, 124 insertions(+), 161 deletions(-)
diff --git a/drivers/ssb/Kconfig b/drivers/ssb/Kconfig
index 42cdaa9..9c2ef55 100644
--- a/drivers/ssb/Kconfig
+++ b/drivers/ssb/Kconfig
@@ -80,21 +80,9 @@ config SSB_SDIOHOST
If unsure, say N
-config SSB_SILENT
- bool "No SSB kernel messages"
- depends on SSB && EXPERT
- help
- This option turns off all Sonics Silicon Backplane printks.
- Note that you won't be able to identify problems, once
- messages are turned off.
- This might only be desired for production kernels on
- embedded devices to reduce the kernel size.
-
- Say N
-
config SSB_DEBUG
bool "SSB debugging"
- depends on SSB && !SSB_SILENT
+ depends on SSB
help
This turns on additional runtime checks and debugging
messages. Turn this on for SSB troubleshooting.
diff --git a/drivers/ssb/driver_chipcommon.c b/drivers/ssb/driver_chipcommon.c
index 7c031fd..65a99e3 100644
--- a/drivers/ssb/driver_chipcommon.c
+++ b/drivers/ssb/driver_chipcommon.c
@@ -259,7 +259,7 @@ void ssb_chipcommon_init(struct ssb_chipcommon *cc)
return; /* We don't have a ChipCommon */
if (cc->dev->id.revision >= 11)
cc->status = chipco_read32(cc, SSB_CHIPCO_CHIPSTAT);
- ssb_dprintk(KERN_INFO PFX "chipcommon status is 0x%x\n", cc->status);
+ pr_debug("chipcommon status is 0x%x\n", cc->status);
ssb_pmu_init(cc);
chipco_powercontrol_init(cc);
ssb_chipco_set_clockmode(cc, SSB_CLKMODE_FAST);
diff --git a/drivers/ssb/driver_chipcommon_pmu.c b/drivers/ssb/driver_chipcommon_pmu.c
index 5732bb2..c501615 100644
--- a/drivers/ssb/driver_chipcommon_pmu.c
+++ b/drivers/ssb/driver_chipcommon_pmu.c
@@ -8,6 +8,8 @@
* Licensed under the GNU/GPL. See COPYING for details.
*/
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
#include <linux/ssb/ssb.h>
#include <linux/ssb/ssb_regs.h>
#include <linux/ssb/ssb_driver_chipcommon.h>
@@ -110,8 +112,8 @@ static void ssb_pmu0_pllinit_r0(struct ssb_chipcommon *cc,
return;
}
- ssb_printk(KERN_INFO PFX "Programming PLL to %u.%03u MHz\n",
- (crystalfreq / 1000), (crystalfreq % 1000));
+ pr_info("Programming PLL to %u.%03u MHz\n",
+ (crystalfreq / 1000), (crystalfreq % 1000));
/* First turn the PLL off. */
switch (bus->chip_id) {
@@ -138,7 +140,7 @@ static void ssb_pmu0_pllinit_r0(struct ssb_chipcommon *cc,
}
tmp = chipco_read32(cc, SSB_CHIPCO_CLKCTLST);
if (tmp & SSB_CHIPCO_CLKCTLST_HAVEHT)
- ssb_printk(KERN_EMERG PFX "Failed to turn the PLL off!\n");
+ pr_emerg("Failed to turn the PLL off!\n");
/* Set PDIV in PLL control 0. */
pllctl = ssb_chipco_pll_read(cc, SSB_PMU0_PLLCTL0);
@@ -249,8 +251,8 @@ static void ssb_pmu1_pllinit_r0(struct ssb_chipcommon *cc,
return;
}
- ssb_printk(KERN_INFO PFX "Programming PLL to %u.%03u MHz\n",
- (crystalfreq / 1000), (crystalfreq % 1000));
+ pr_info("Programming PLL to %u.%03u MHz\n",
+ (crystalfreq / 1000), (crystalfreq % 1000));
/* First turn the PLL off. */
switch (bus->chip_id) {
@@ -275,7 +277,7 @@ static void ssb_pmu1_pllinit_r0(struct ssb_chipcommon *cc,
}
tmp = chipco_read32(cc, SSB_CHIPCO_CLKCTLST);
if (tmp & SSB_CHIPCO_CLKCTLST_HAVEHT)
- ssb_printk(KERN_EMERG PFX "Failed to turn the PLL off!\n");
+ pr_emerg("Failed to turn the PLL off!\n");
/* Set p1div and p2div. */
pllctl = ssb_chipco_pll_read(cc, SSB_PMU1_PLLCTL0);
@@ -339,9 +341,7 @@ static void ssb_pmu_pll_init(struct ssb_chipcommon *cc)
}
break;
default:
- ssb_printk(KERN_ERR PFX
- "ERROR: PLL init unknown for device %04X\n",
- bus->chip_id);
+ pr_err("PLL init unknown for device %04X\n", bus->chip_id);
}
}
@@ -459,9 +459,8 @@ static void ssb_pmu_resources_init(struct ssb_chipcommon *cc)
max_msk = 0xFFFFF;
break;
default:
- ssb_printk(KERN_ERR PFX
- "ERROR: PMU resource config unknown for device %04X\n",
- bus->chip_id);
+ pr_err("PMU resource config unknown for device %04X\n",
+ bus->chip_id);
}
if (updown_tab) {
@@ -513,8 +512,8 @@ void ssb_pmu_init(struct ssb_chipcommon *cc)
pmucap = chipco_read32(cc, SSB_CHIPCO_PMU_CAP);
cc->pmu.rev = (pmucap & SSB_CHIPCO_PMU_CAP_REVISION);
- ssb_dprintk(KERN_DEBUG PFX "Found rev %u PMU (capabilities 0x%08X)\n",
- cc->pmu.rev, pmucap);
+ pr_debug("Found rev %u PMU (capabilities 0x%08X)\n",
+ cc->pmu.rev, pmucap);
if (cc->pmu.rev == 1)
chipco_mask32(cc, SSB_CHIPCO_PMU_CTL,
diff --git a/drivers/ssb/driver_mipscore.c b/drivers/ssb/driver_mipscore.c
index 97efce1..97520ff 100644
--- a/drivers/ssb/driver_mipscore.c
+++ b/drivers/ssb/driver_mipscore.c
@@ -147,21 +147,23 @@ static void set_irq(struct ssb_device *dev, unsigned int irq)
irqflag |= (ipsflag & ~ipsflag_irq_mask[irq]);
ssb_write32(mdev, SSB_IPSFLAG, irqflag);
}
- ssb_dprintk(KERN_INFO PFX
- "set_irq: core 0x%04x, irq %d => %d\n",
- dev->id.coreid, oldirq+2, irq+2);
+ pr_debug("set_irq: core 0x%04x, irq %d => %d\n",
+ dev->id.coreid, oldirq+2, irq+2);
}
static void print_irq(struct ssb_device *dev, unsigned int irq)
{
+#ifdef DEBUG
int i;
- static const char *irq_name[] = {"2(S)", "3", "4", "5", "6", "D", "I"};
- ssb_dprintk(KERN_INFO PFX
- "core 0x%04x, irq :", dev->id.coreid);
+ static const char * const irq_name[] = {
+ "2(S)", "3", "4", "5", "6", "D", "I"
+ };
+ pr_debug("core 0x%04x, irq :", dev->id.coreid);
for (i = 0; i <= 6; i++) {
- ssb_dprintk(" %s%s", irq_name[i], i==irq?"*":" ");
+ pr_cont(" %s%s", irq_name[i], i==irq?"*":" ");
}
- ssb_dprintk("\n");
+ pr_cont("\n");
+#endif
}
static void dump_irq(struct ssb_bus *bus)
@@ -238,7 +240,7 @@ void ssb_mipscore_init(struct ssb_mipscore *mcore)
if (!mcore->dev)
return; /* We don't have a MIPS core */
- ssb_dprintk(KERN_INFO PFX "Initializing MIPS core...\n");
+ pr_debug("Initializing MIPS core...\n");
bus = mcore->dev->bus;
hz = ssb_clockspeed(bus);
@@ -286,7 +288,7 @@ void ssb_mipscore_init(struct ssb_mipscore *mcore)
break;
}
}
- ssb_dprintk(KERN_INFO PFX "after irq reconfiguration\n");
+ pr_debug("after irq reconfiguration\n");
dump_irq(bus);
ssb_mips_serial_init(mcore);
diff --git a/drivers/ssb/driver_pcicore.c b/drivers/ssb/driver_pcicore.c
index 1ba9f0e..38d7215 100644
--- a/drivers/ssb/driver_pcicore.c
+++ b/drivers/ssb/driver_pcicore.c
@@ -8,6 +8,8 @@
* Licensed under the GNU/GPL. See COPYING for details.
*/
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
#include <linux/ssb/ssb.h>
#include <linux/pci.h>
#include <linux/delay.h>
@@ -262,8 +264,7 @@ int ssb_pcicore_plat_dev_init(struct pci_dev *d)
return -ENODEV;
}
- ssb_printk(KERN_INFO "PCI: Fixing up device %s\n",
- pci_name(d));
+ pr_info("PCI: Fixing up device %s\n", pci_name(d));
/* Fix up interrupt lines */
d->irq = ssb_mips_irq(extpci_core->dev) + 2;
@@ -284,12 +285,12 @@ static void ssb_pcicore_fixup_pcibridge(struct pci_dev *dev)
if (dev->bus->number != 0 || PCI_SLOT(dev->devfn) != 0)
return;
- ssb_printk(KERN_INFO "PCI: Fixing up bridge %s\n", pci_name(dev));
+ pr_info("PCI: Fixing up bridge %s\n", pci_name(dev));
/* Enable PCI bridge bus mastering and memory space */
pci_set_master(dev);
if (pcibios_enable_device(dev, ~0) < 0) {
- ssb_printk(KERN_ERR "PCI: SSB bridge enable failed\n");
+ pr_err("PCI: SSB bridge enable failed\n");
return;
}
@@ -298,7 +299,7 @@ static void ssb_pcicore_fixup_pcibridge(struct pci_dev *dev)
/* Make sure our latency is high enough to handle the devices behind us */
lat = 168;
- ssb_printk(KERN_INFO "PCI: Fixing latency timer of device %s to %u\n",
+ pr_info("PCI: Fixing latency timer of device %s to %u\n",
pci_name(dev), lat);
pci_write_config_byte(dev, PCI_LATENCY_TIMER, lat);
}
@@ -322,7 +323,7 @@ static void ssb_pcicore_init_hostmode(struct ssb_pcicore *pc)
return;
extpci_core = pc;
- ssb_dprintk(KERN_INFO PFX "PCIcore in host mode found\n");
+ pr_debug("PCIcore in host mode found\n");
/* Reset devices on the external PCI bus */
val = SSB_PCICORE_CTL_RST_OE;
val |= SSB_PCICORE_CTL_CLK_OE;
@@ -337,7 +338,7 @@ static void ssb_pcicore_init_hostmode(struct ssb_pcicore *pc)
udelay(1); /* Assertion time demanded by the PCI standard */
if (pc->dev->bus->has_cardbus_slot) {
- ssb_dprintk(KERN_INFO PFX "CardBus slot detected\n");
+ pr_debug("CardBus slot detected\n");
pc->cardbusmode = 1;
/* GPIO 1 resets the bridge */
ssb_gpio_out(pc->dev->bus, 1, 1);
diff --git a/drivers/ssb/main.c b/drivers/ssb/main.c
index e05ba6e..be4fc17 100644
--- a/drivers/ssb/main.c
+++ b/drivers/ssb/main.c
@@ -8,6 +8,8 @@
* Licensed under the GNU/GPL. See COPYING for details.
*/
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
#include "ssb_private.h"
#include <linux/delay.h>
@@ -288,8 +290,8 @@ int ssb_devices_thaw(struct ssb_freeze_context *ctx)
err = sdrv->probe(sdev, &sdev->id);
if (err) {
- ssb_printk(KERN_ERR PFX "Failed to thaw device %s\n",
- dev_name(sdev->dev));
+ pr_err("Failed to thaw device %s\n",
+ dev_name(sdev->dev));
result = err;
}
ssb_driver_put(sdrv);
@@ -497,8 +499,7 @@ static int ssb_devices_register(struct ssb_bus *bus)
devwrap = kzalloc(sizeof(*devwrap), GFP_KERNEL);
if (!devwrap) {
- ssb_printk(KERN_ERR PFX
- "Could not allocate device\n");
+ pr_err("Could not allocate device\n");
err = -ENOMEM;
goto error;
}
@@ -537,9 +538,7 @@ static int ssb_devices_register(struct ssb_bus *bus)
sdev->dev = dev;
err = device_register(dev);
if (err) {
- ssb_printk(KERN_ERR PFX
- "Could not register %s\n",
- dev_name(dev));
+ pr_err("Could not register %s\n", dev_name(dev));
/* Set dev to NULL to not unregister
* dev on error unwinding. */
sdev->dev = NULL;
@@ -862,11 +861,11 @@ int ssb_bus_pcibus_register(struct ssb_bus *bus,
err = ssb_bus_register(bus, ssb_pci_get_invariants, 0);
if (!err) {
- ssb_printk(KERN_INFO PFX "Sonics Silicon Backplane found on "
- "PCI device %s\n", dev_name(&host_pci->dev));
+ pr_info("Sonics Silicon Backplane found on PCI device %s\n",
+ dev_name(&host_pci->dev));
} else {
- ssb_printk(KERN_ERR PFX "Failed to register PCI version"
- " of SSB with error %d\n", err);
+ pr_err("Failed to register PCI version of SSB with error %d\n",
+ err);
}
return err;
@@ -887,8 +886,8 @@ int ssb_bus_pcmciabus_register(struct ssb_bus *bus,
err = ssb_bus_register(bus, ssb_pcmcia_get_invariants, baseaddr);
if (!err) {
- ssb_printk(KERN_INFO PFX "Sonics Silicon Backplane found on "
- "PCMCIA device %s\n", pcmcia_dev->devname);
+ pr_info("Sonics Silicon Backplane found on PCMCIA device %s\n",
+ pcmcia_dev->devname);
}
return err;
@@ -909,8 +908,8 @@ int ssb_bus_sdiobus_register(struct ssb_bus *bus, struct sdio_func *func,
err = ssb_bus_register(bus, ssb_sdio_get_invariants, ~0);
if (!err) {
- ssb_printk(KERN_INFO PFX "Sonics Silicon Backplane found on "
- "SDIO device %s\n", sdio_func_id(func));
+ pr_info("Sonics Silicon Backplane found on SDIO device %s\n",
+ sdio_func_id(func));
}
return err;
@@ -929,8 +928,8 @@ int ssb_bus_ssbbus_register(struct ssb_bus *bus,
err = ssb_bus_register(bus, get_invariants, baseaddr);
if (!err) {
- ssb_printk(KERN_INFO PFX "Sonics Silicon Backplane found at "
- "address 0x%08lX\n", baseaddr);
+ pr_info("Sonics Silicon Backplane found at address 0x%08lX\n",
+ baseaddr);
}
return err;
@@ -1130,7 +1129,7 @@ static u32 ssb_tmslow_reject_bitmask(struct ssb_device *dev)
case SSB_IDLOW_SSBREV_27: /* same here */
return SSB_TMSLOW_REJECT_23; /* this is a guess */
default:
- printk(KERN_INFO "ssb: Backplane Revision 0x%.8X\n", rev);
+ pr_info("Backplane Revision 0x%.8X\n", rev);
WARN_ON(1);
}
return (SSB_TMSLOW_REJECT_22 | SSB_TMSLOW_REJECT_23);
@@ -1211,8 +1210,7 @@ static int ssb_wait_bits(struct ssb_device *dev, u16 reg, u32 bitmask,
}
udelay(10);
}
- printk(KERN_ERR PFX "Timeout waiting for bitmask %08X on "
- "register %04X to %s.\n",
+ pr_err("Timeout waiting for bitmask %08X on register %04X to %s\n",
bitmask, reg, (set ? "set" : "clear"));
return -ETIMEDOUT;
@@ -1302,7 +1300,7 @@ out:
#endif
return err;
error:
- ssb_printk(KERN_ERR PFX "Bus powerdown failed\n");
+ pr_err("Bus powerdown failed\n");
goto out;
}
EXPORT_SYMBOL(ssb_bus_may_powerdown);
@@ -1325,7 +1323,7 @@ int ssb_bus_powerup(struct ssb_bus *bus, bool dynamic_pctl)
#endif
return 0;
error:
- ssb_printk(KERN_ERR PFX "Bus powerup failed\n");
+ pr_err("Bus powerup failed\n");
return err;
}
EXPORT_SYMBOL(ssb_bus_powerup);
@@ -1402,15 +1400,13 @@ static int __init ssb_modinit(void)
err = b43_pci_ssb_bridge_init();
if (err) {
- ssb_printk(KERN_ERR "Broadcom 43xx PCI-SSB-bridge "
- "initialization failed\n");
+ pr_err("Broadcom 43xx PCI-SSB-bridge initialization failed\n");
/* don't fail SSB init because of this */
err = 0;
}
err = ssb_gige_init();
if (err) {
- ssb_printk(KERN_ERR "SSB Broadcom Gigabit Ethernet "
- "driver initialization failed\n");
+ pr_err("SSB Broadcom Gigabit Ethernet driver initialization failed\n");
/* don't fail SSB init because of this */
err = 0;
}
diff --git a/drivers/ssb/pci.c b/drivers/ssb/pci.c
index a467b20..04a2834 100644
--- a/drivers/ssb/pci.c
+++ b/drivers/ssb/pci.c
@@ -15,6 +15,8 @@
* Licensed under the GNU/GPL. See COPYING for details.
*/
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
#include <linux/ssb/ssb.h>
#include <linux/ssb/ssb_regs.h>
#include <linux/slab.h>
@@ -56,7 +58,7 @@ int ssb_pci_switch_coreidx(struct ssb_bus *bus, u8 coreidx)
}
return 0;
error:
- ssb_printk(KERN_ERR PFX "Failed to switch to core %u\n", coreidx);
+ pr_err("Failed to switch to core %u\n", coreidx);
return -ENODEV;
}
@@ -67,10 +69,8 @@ int ssb_pci_switch_core(struct ssb_bus *bus,
unsigned long flags;
#if SSB_VERBOSE_PCICORESWITCH_DEBUG
- ssb_printk(KERN_INFO PFX
- "Switching to %s core, index %d\n",
- ssb_core_name(dev->id.coreid),
- dev->core_index);
+ pr_info("Switching to %s core, index %d\n",
+ ssb_core_name(dev->id.coreid), dev->core_index);
#endif
spin_lock_irqsave(&bus->bar_lock, flags);
@@ -162,7 +162,7 @@ out:
return err;
err_pci:
- printk(KERN_ERR PFX "Error: ssb_pci_xtal() could not access PCI config space!\n");
+ pr_err("Error: ssb_pci_xtal() could not access PCI config space!\n");
err = -EBUSY;
goto out;
}
@@ -266,7 +266,7 @@ static int sprom_do_write(struct ssb_bus *bus, const u16 *sprom)
u32 spromctl;
u16 size = bus->sprom_size;
- ssb_printk(KERN_NOTICE PFX "Writing SPROM. Do NOT turn off the power! Please stand by...\n");
+ pr_notice("Writing SPROM. Do NOT turn off the power! Please stand by...\n");
err = pci_read_config_dword(pdev, SSB_SPROMCTL, &spromctl);
if (err)
goto err_ctlreg;
@@ -274,17 +274,17 @@ static int sprom_do_write(struct ssb_bus *bus, const u16 *sprom)
err = pci_write_config_dword(pdev, SSB_SPROMCTL, spromctl);
if (err)
goto err_ctlreg;
- ssb_printk(KERN_NOTICE PFX "[ 0%%");
+ pr_notice("[ 0%%");
msleep(500);
for (i = 0; i < size; i++) {
if (i == size / 4)
- ssb_printk("25%%");
+ printk("25%%");
else if (i == size / 2)
- ssb_printk("50%%");
+ printk("50%%");
else if (i == (size * 3) / 4)
- ssb_printk("75%%");
+ printk("75%%");
else if (i % 2)
- ssb_printk(".");
+ printk(".");
writew(sprom[i], bus->mmio + bus->sprom_offset + (i * 2));
mmiowb();
msleep(20);
@@ -297,12 +297,12 @@ static int sprom_do_write(struct ssb_bus *bus, const u16 *sprom)
if (err)
goto err_ctlreg;
msleep(500);
- ssb_printk("100%% ]\n");
- ssb_printk(KERN_NOTICE PFX "SPROM written.\n");
+ printk("100%% ]\n");
+ pr_notice("SPROM written\n");
return 0;
err_ctlreg:
- ssb_printk(KERN_ERR PFX "Could not access SPROM control register.\n");
+ pr_err("Could not access SPROM control register\n");
return err;
}
@@ -618,7 +618,7 @@ static int sprom_extract(struct ssb_bus *bus, struct ssb_sprom *out,
memset(out, 0, sizeof(*out));
out->revision = in[size - 1] & 0x00FF;
- ssb_dprintk(KERN_DEBUG PFX "SPROM revision %d detected.\n", out->revision);
+ pr_debug("SPROM revision %d detected\n", out->revision);
memset(out->et0mac, 0xFF, 6); /* preset et0 and et1 mac */
memset(out->et1mac, 0xFF, 6);
@@ -627,7 +627,7 @@ static int sprom_extract(struct ssb_bus *bus, struct ssb_sprom *out,
* number stored in the SPROM.
* Always extract r1. */
out->revision = 1;
- ssb_dprintk(KERN_DEBUG PFX "SPROM treated as revision %d\n", out->revision);
+ pr_debug("SPROM treated as revision %d\n", out->revision);
}
switch (out->revision) {
@@ -644,9 +644,8 @@ static int sprom_extract(struct ssb_bus *bus, struct ssb_sprom *out,
sprom_extract_r8(out, in);
break;
default:
- ssb_printk(KERN_WARNING PFX "Unsupported SPROM"
- " revision %d detected. Will extract"
- " v1\n", out->revision);
+ pr_warn("Unsupported SPROM revision %d detected. Will extract v1\n",
+ out->revision);
out->revision = 1;
sprom_extract_r123(out, in);
}
@@ -667,7 +666,7 @@ static int ssb_pci_sprom_get(struct ssb_bus *bus,
u16 *buf;
if (!ssb_is_sprom_available(bus)) {
- ssb_printk(KERN_ERR PFX "No SPROM available!\n");
+ pr_err("No SPROM available!\n");
return -ENODEV;
}
if (bus->chipco.dev) { /* can be unavailible! */
@@ -686,7 +685,7 @@ static int ssb_pci_sprom_get(struct ssb_bus *bus,
} else {
bus->sprom_offset = SSB_SPROM_BASE1;
}
- ssb_dprintk(KERN_INFO PFX "SPROM offset is 0x%x\n", bus->sprom_offset);
+ pr_debug("SPROM offset is 0x%x\n", bus->sprom_offset);
buf = kcalloc(SSB_SPROMSIZE_WORDS_R123, sizeof(u16), GFP_KERNEL);
if (!buf)
@@ -714,8 +713,7 @@ static int ssb_pci_sprom_get(struct ssb_bus *bus,
err = 0;
goto out_free;
}
- ssb_printk(KERN_WARNING PFX "WARNING: Invalid"
- " SPROM CRC (corrupt SPROM)\n");
+ pr_warn("WARNING: Invalid SPROM CRC (corrupt SPROM)\n");
}
}
err = sprom_extract(bus, sprom, buf, bus->sprom_size);
@@ -756,8 +754,7 @@ static int ssb_pci_assert_buspower(struct ssb_bus *bus)
if (likely(bus->powered_up))
return 0;
- printk(KERN_ERR PFX "FATAL ERROR: Bus powered down "
- "while accessing PCI MMIO space\n");
+ pr_err("FATAL ERROR: Bus powered down while accessing PCI MMIO space\n");
if (bus->power_warn_count <= 10) {
bus->power_warn_count++;
dump_stack();
diff --git a/drivers/ssb/pcmcia.c b/drivers/ssb/pcmcia.c
index f853379..03cf0a0 100644
--- a/drivers/ssb/pcmcia.c
+++ b/drivers/ssb/pcmcia.c
@@ -8,6 +8,8 @@
* Licensed under the GNU/GPL. See COPYING for details.
*/
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
#include <linux/ssb/ssb.h>
#include <linux/delay.h>
#include <linux/io.h>
@@ -143,7 +145,7 @@ int ssb_pcmcia_switch_coreidx(struct ssb_bus *bus,
return 0;
error:
- ssb_printk(KERN_ERR PFX "Failed to switch to core %u\n", coreidx);
+ pr_err("Failed to switch to core %u\n", coreidx);
return err;
}
@@ -153,10 +155,8 @@ int ssb_pcmcia_switch_core(struct ssb_bus *bus,
int err;
#if SSB_VERBOSE_PCMCIACORESWITCH_DEBUG
- ssb_printk(KERN_INFO PFX
- "Switching to %s core, index %d\n",
- ssb_core_name(dev->id.coreid),
- dev->core_index);
+ pr_info("Switching to %s core, index %d\n",
+ ssb_core_name(dev->id.coreid), dev->core_index);
#endif
err = ssb_pcmcia_switch_coreidx(bus, dev->core_index);
@@ -192,7 +192,7 @@ int ssb_pcmcia_switch_segment(struct ssb_bus *bus, u8 seg)
return 0;
error:
- ssb_printk(KERN_ERR PFX "Failed to switch pcmcia segment\n");
+ pr_err("Failed to switch pcmcia segment\n");
return err;
}
@@ -549,44 +549,39 @@ static int ssb_pcmcia_sprom_write_all(struct ssb_bus *bus, const u16 *sprom)
bool failed = 0;
size_t size = SSB_PCMCIA_SPROM_SIZE;
- ssb_printk(KERN_NOTICE PFX
- "Writing SPROM. Do NOT turn off the power! "
- "Please stand by...\n");
+ pr_notice("Writing SPROM. Do NOT turn off the power! Please stand by...\n");
err = ssb_pcmcia_sprom_command(bus, SSB_PCMCIA_SPROMCTL_WRITEEN);
if (err) {
- ssb_printk(KERN_NOTICE PFX
- "Could not enable SPROM write access.\n");
+ pr_notice("Could not enable SPROM write access\n");
return -EBUSY;
}
- ssb_printk(KERN_NOTICE PFX "[ 0%%");
+ pr_notice("[ 0%%");
msleep(500);
for (i = 0; i < size; i++) {
if (i == size / 4)
- ssb_printk("25%%");
+ printk("25%%");
else if (i == size / 2)
- ssb_printk("50%%");
+ printk("50%%");
else if (i == (size * 3) / 4)
- ssb_printk("75%%");
+ printk("75%%");
else if (i % 2)
- ssb_printk(".");
+ printk(".");
err = ssb_pcmcia_sprom_write(bus, i, sprom[i]);
if (err) {
- ssb_printk(KERN_NOTICE PFX
- "Failed to write to SPROM.\n");
+ pr_notice("Failed to write to SPROM\n");
failed = 1;
break;
}
}
err = ssb_pcmcia_sprom_command(bus, SSB_PCMCIA_SPROMCTL_WRITEDIS);
if (err) {
- ssb_printk(KERN_NOTICE PFX
- "Could not disable SPROM write access.\n");
+ pr_notice("Could not disable SPROM write access\n");
failed = 1;
}
msleep(500);
if (!failed) {
- ssb_printk("100%% ]\n");
- ssb_printk(KERN_NOTICE PFX "SPROM written.\n");
+ printk("100%% ]\n");
+ pr_notice("SPROM written\n");
}
return failed ? -EBUSY : 0;
@@ -704,9 +699,8 @@ static int ssb_pcmcia_do_get_invariants(struct pcmcia_device *p_dev,
return -ENOSPC; /* continue with next entry */
error:
- ssb_printk(KERN_ERR PFX
- "PCMCIA: Failed to fetch device invariants: %s\n",
- error_description);
+ pr_err("PCMCIA: Failed to fetch device invariants: %s\n",
+ error_description);
return -ENODEV;
}
@@ -726,8 +720,7 @@ int ssb_pcmcia_get_invariants(struct ssb_bus *bus,
res = pcmcia_loop_tuple(bus->host_pcmcia, CISTPL_FUNCE,
ssb_pcmcia_get_mac, sprom);
if (res != 0) {
- ssb_printk(KERN_ERR PFX
- "PCMCIA: Failed to fetch MAC address\n");
+ pr_err("PCMCIA: Failed to fetch MAC address\n");
return -ENODEV;
}
@@ -737,8 +730,7 @@ int ssb_pcmcia_get_invariants(struct ssb_bus *bus,
if ((res == 0) || (res == -ENOSPC))
return 0;
- ssb_printk(KERN_ERR PFX
- "PCMCIA: Failed to fetch device invariants\n");
+ pr_err("PCMCIA: Failed to fetch device invariants\n");
return -ENODEV;
}
@@ -847,6 +839,6 @@ int ssb_pcmcia_init(struct ssb_bus *bus)
return 0;
error:
- ssb_printk(KERN_ERR PFX "Failed to initialize PCMCIA host device\n");
+ pr_err("Failed to initialize PCMCIA host device\n");
return err;
}
diff --git a/drivers/ssb/scan.c b/drivers/ssb/scan.c
index 7dca719..3e670f4 100644
--- a/drivers/ssb/scan.c
+++ b/drivers/ssb/scan.c
@@ -12,6 +12,8 @@
* Licensed under the GNU/GPL. See COPYING for details.
*/
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
#include <linux/ssb/ssb.h>
#include <linux/ssb/ssb_regs.h>
#include <linux/pci.h>
@@ -123,8 +125,7 @@ static u16 pcidev_to_chipid(struct pci_dev *pci_dev)
chipid_fallback = 0x4401;
break;
default:
- ssb_printk(KERN_ERR PFX
- "PCI-ID not in fallback list\n");
+ pr_err("PCI-ID not in fallback list\n");
}
return chipid_fallback;
@@ -150,8 +151,7 @@ static u8 chipid_to_nrcores(u16 chipid)
case 0x4704:
return 9;
default:
- ssb_printk(KERN_ERR PFX
- "CHIPID not in nrcores fallback list\n");
+ pr_err("CHIPID not in nrcores fallback list\n");
}
return 1;
@@ -319,9 +319,8 @@ int ssb_bus_scan(struct ssb_bus *bus,
if (!bus->nr_devices)
bus->nr_devices = chipid_to_nrcores(bus->chip_id);
if (bus->nr_devices > ARRAY_SIZE(bus->devices)) {
- ssb_printk(KERN_ERR PFX
- "More than %d ssb cores found (%d)\n",
- SSB_MAX_NR_CORES, bus->nr_devices);
+ pr_err("More than %d ssb cores found (%d)\n",
+ SSB_MAX_NR_CORES, bus->nr_devices);
goto err_unmap;
}
if (bus->bustype == SSB_BUSTYPE_SSB) {
@@ -353,18 +352,17 @@ int ssb_bus_scan(struct ssb_bus *bus,
dev->ops = bus->ops;
printk(KERN_DEBUG PFX
- "Core %d found: %s "
- "(cc 0x%03X, rev 0x%02X, vendor 0x%04X)\n",
- i, ssb_core_name(dev->id.coreid),
- dev->id.coreid, dev->id.revision, dev->id.vendor);
+ "Core %d found: %s "
+ "(cc 0x%03X, rev 0x%02X, vendor 0x%04X)\n",
+ i, ssb_core_name(dev->id.coreid),
+ dev->id.coreid, dev->id.revision, dev->id.vendor);
switch (dev->id.coreid) {
case SSB_DEV_80211:
nr_80211_cores++;
if (nr_80211_cores > 1) {
if (!we_support_multiple_80211_cores(bus)) {
- ssb_dprintk(KERN_INFO PFX "Ignoring additional "
- "802.11 core\n");
+ pr_debug("Ignoring additional 802.11 core\n");
continue;
}
}
@@ -372,8 +370,7 @@ int ssb_bus_scan(struct ssb_bus *bus,
case SSB_DEV_EXTIF:
#ifdef CONFIG_SSB_DRIVER_EXTIF
if (bus->extif.dev) {
- ssb_printk(KERN_WARNING PFX
- "WARNING: Multiple EXTIFs found\n");
+ pr_warn("WARNING: Multiple EXTIFs found\n");
break;
}
bus->extif.dev = dev;
@@ -381,8 +378,7 @@ int ssb_bus_scan(struct ssb_bus *bus,
break;
case SSB_DEV_CHIPCOMMON:
if (bus->chipco.dev) {
- ssb_printk(KERN_WARNING PFX
- "WARNING: Multiple ChipCommon found\n");
+ pr_warn("WARNING: Multiple ChipCommon found\n");
break;
}
bus->chipco.dev = dev;
@@ -391,8 +387,7 @@ int ssb_bus_scan(struct ssb_bus *bus,
case SSB_DEV_MIPS_3302:
#ifdef CONFIG_SSB_DRIVER_MIPS
if (bus->mipscore.dev) {
- ssb_printk(KERN_WARNING PFX
- "WARNING: Multiple MIPS cores found\n");
+ pr_warn("WARNING: Multiple MIPS cores found\n");
break;
}
bus->mipscore.dev = dev;
@@ -413,8 +408,7 @@ int ssb_bus_scan(struct ssb_bus *bus,
}
}
if (bus->pcicore.dev) {
- ssb_printk(KERN_WARNING PFX
- "WARNING: Multiple PCI(E) cores found\n");
+ pr_warn("WARNING: Multiple PCI(E) cores found\n");
break;
}
bus->pcicore.dev = dev;
diff --git a/drivers/ssb/sprom.c b/drivers/ssb/sprom.c
index 4f7cc8d..ca86c68 100644
--- a/drivers/ssb/sprom.c
+++ b/drivers/ssb/sprom.c
@@ -11,6 +11,8 @@
* Licensed under the GNU/GPL. See COPYING for details.
*/
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
#include "ssb_private.h"
#include <linux/ctype.h>
@@ -127,13 +129,13 @@ ssize_t ssb_attr_sprom_store(struct ssb_bus *bus,
goto out_kfree;
err = ssb_devices_freeze(bus, &freeze);
if (err) {
- ssb_printk(KERN_ERR PFX "SPROM write: Could not freeze all devices\n");
+ pr_err("SPROM write: Could not freeze all devices\n");
goto out_unlock;
}
res = sprom_write(bus, sprom);
err = ssb_devices_thaw(&freeze);
if (err)
- ssb_printk(KERN_ERR PFX "SPROM write: Could not thaw all devices\n");
+ pr_err("SPROM write: Could not thaw all devices\n");
out_unlock:
mutex_unlock(&bus->sprom_mutex);
out_kfree:
diff --git a/drivers/ssb/ssb_private.h b/drivers/ssb/ssb_private.h
index 0331139..30f0ca5 100644
--- a/drivers/ssb/ssb_private.h
+++ b/drivers/ssb/ssb_private.h
@@ -7,17 +7,9 @@
#define PFX "ssb: "
-#ifdef CONFIG_SSB_SILENT
-# define ssb_printk(fmt, x...) do { /* nothing */ } while (0)
-#else
-# define ssb_printk printk
-#endif /* CONFIG_SSB_SILENT */
-
/* dprintk: Debugging printk; vanishes for non-debug compilation */
#ifdef CONFIG_SSB_DEBUG
-# define ssb_dprintk(fmt, x...) ssb_printk(fmt , ##x)
-#else
-# define ssb_dprintk(fmt, x...) do { /* nothing */ } while (0)
+# define DEBUG
#endif
#ifdef CONFIG_SSB_DEBUG
diff --git a/include/linux/ssb/ssb.h b/include/linux/ssb/ssb.h
index 7e99b34..7d65330 100644
--- a/include/linux/ssb/ssb.h
+++ b/include/linux/ssb/ssb.h
@@ -477,8 +477,8 @@ extern u32 ssb_dma_translation(struct ssb_device *dev);
static inline void __cold __ssb_dma_not_implemented(struct ssb_device *dev)
{
#ifdef CONFIG_SSB_DEBUG
- printk(KERN_ERR "SSB: BUG! Calling DMA API for "
- "unsupported bustype %d\n", dev->bus->bustype);
+ pr_err("BUG! Calling DMA API for unsupported bustype %d\n",
+ dev->bus->bustype);
#endif /* DEBUG */
}
^ permalink raw reply related
* Re: [PWM v9 2/3] PWM: GPIO+hrtimer device emulation
From: Ben Gardiner @ 2011-04-06 13:17 UTC (permalink / raw)
To: Bill Gatliff; +Cc: linux-kernel, linux-embedded
In-Reply-To: <1301630392-20793-3-git-send-email-bgat@billgatliff.com>
Hi Bill,
I tested this series on da850evm from commit
60124de319dc2800c0f75f44bf84471a8de0dbc5 of
git://git.billgatliff.com/pwm.git.
With the following commands I was able to observe the production of a
100Hz 50% output on a GPIO (specifically GPIO #125 which is GPIO7[13]
on the SoC and J15[13] on the baseboard for other da850 users).
mount config /config -t configfs
mkdir /config/gpio_pwm/125
echo 1 > /sys/class/pwm/gpio_pwm\:125/export
echo 10000000 > /sys/class/pwm/gpio_pwm\:125/period_ns
echo 5000000 > /sys/class/pwm/gpio_pwm\:125/duty_ns
echo 1 > /sys/class/pwm/gpio_pwm\:125/run
I noticed that the following command did not stop the pwm output.
echo 0 > /sys/class/pwm/gpio_pwm\:125/run
But the following command did (and I did not need to echo 1 >
/sys/class/pwm/gpio_pwm\:125/unexport first)
rm -rf /config/gpio_pwm/125
The observations from my testing above are repeated in context below:
On Thu, Mar 31, 2011 at 11:59 PM, Bill Gatliff <bgat@billgatliff.com> wrote:
> Emulates a PWM device using a GPIO pin and an hrtimer. Subject
> to CPU, scheduler and hardware limitations, can support many
> PWM outputs, e.g. as many as you have GPIO pins available for.
>
> On a 200 MHz ARM9 processor, a PWM frequency of 100 Hz can be attained
> with this code so long as the duty cycle remains between about 20-80%.
> At higher or lower duty cycles, the transition events may arrive too
> close for the scheduler and CPU to reliably service.
>
> This driver supports creation of new GPIO+hrtimer PWM devices via
> configfs:
>
> # mount config /config -t configfs
> # mkdir /config/gpio-pwm/<gpio number>
minor: I observed that the configfs directory was 'gpio_pwm' not 'gpio-pwm.'
> The new PWM device will appear as /sys/class/pwm/gpio-pwm.<gpio number>.
>
> Caveats:
> * The GPIO pin number must be valid, not already in use
> * The output state of the GPIO pin is configured when the PWM starts
> running i.e. not immediately upon request, because the polarity of
> the inactive state of the pin isn't known until the pwm device's
> 'polarity' attribute is configured
> * After creating and binding the pwm device, you must then request
> it by writing to /sys/class/pwm/gpio-pwm.<gpio number>/export
>
> Unbind and destroy the pwm device by first stopping and unexporting
> the pwm device under sysfs as usual; then do:
I observed that
# echo 0 > /sys/class/pwm/gpio_pwm\:125/run
did not stop the pwm output and that
# echo 1 > /sys/class/pwm/gpio_pwm\:125/unexport
was not required before rm -rf'ing the /config directory:
> # rm -rf /config/gpio-pwm/<gpio number>
> Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
Overall this is a really easy to user userspace interface -- it was a
pleasure to setup and test.
Tested-by: Ben Gardiner <bengardiner@nanometrics.ca>
Best Regards,
Ben Gardiner
---
Nanometrics Inc.
http://www.nanometrics.ca
^ permalink raw reply
* Super Fast Boot of Embedded Linux: 300 ms from boot loader to shell
From: Constantine Shulyupin @ 2011-04-11 14:02 UTC (permalink / raw)
To: linux-embedded
Hello all,
Make Linux Software presents the fastest ever embedded Linux boot for
720 MHz ARM and NAND flash memory. Linux boot time is 300 milliseconds
from boot loader to shell. The first goal of the project is to achieve
a minimal boot time of a minimal but functional Linux system on common
hardware. The second goal is to provide a platform for developing more
functional systems with an even minimized boot time.
Video of boot process: http://youtu.be/747XLVbTgA4
Boot log with timestamps:
0.000 0.000: TI X-Loader 1.4.4ss Mar 26 2011 01:45:43
0.000 0.000: Optimised by www.MakeLinux.com
0.000 0.000: Loading
0.237 0.237: Running
0.237 0.000: CFG_LOADADDR=80008000
0.249 0.012: *(int*)CFG_LOADADDR=e321f0d3
0.276 0.027: Linux version 2.6.32 (const@makelinux.com)
0.276 0.000: Starting application
0.296 0.020: BusyBox v1.16.2 hush - the humble shell
To learn more, please visit http://www.makelinux.com/emb/fastboot/omap
Thanks.
--
Constantine Shulyupin
http://www.MakeLinux.com/
Embedded Linux Systems,
Device Drivers, TI DaVinci
^ permalink raw reply
* Re: Super Fast Boot of Embedded Linux: 300 ms from boot loader to shell
From: Marco Stornelli @ 2011-04-11 16:53 UTC (permalink / raw)
To: Constantine Shulyupin; +Cc: linux-embedded
In-Reply-To: <BANLkTi=Zahmo_d1Fo7JnaD3iJHAuhSAZGQ@mail.gmail.com>
Il 11/04/2011 16:02, Constantine Shulyupin ha scritto:
> Hello all,
>
> Make Linux Software presents the fastest ever embedded Linux boot for
> 720 MHz ARM and NAND flash memory. Linux boot time is 300 milliseconds
> from boot loader to shell. The first goal of the project is to achieve
> a minimal boot time of a minimal but functional Linux system on common
> hardware. The second goal is to provide a platform for developing more
> functional systems with an even minimized boot time.
>
Hi,
is it a marketing announcement or a presentation of an innovative
solution? In the first case don't send this kind of email to this
mailing list. In the second one, could you explain to us your "fast
boot" solution or your kernel modifications? I can't find any
implementation details on the web site.
Marco
^ permalink raw reply
* [Vortex86] Vortex86DX SoC - HIGH_RESOLUTION_TIMER
From: Lukasz Majewski @ 2011-04-12 19:27 UTC (permalink / raw)
To: Lukasz Majewski; +Cc: linux-embedded, linux-8086, platform-driver-x86
Hi all,
I'd like to kindly ask for some help with my embedded system.
I'm using the Vortex86DX SoC (it's a x86 32 clone) on my PC104 board.
I've managed to apply and run the 2.6.33.7.2-rt30 RT linux.
It supports the full preemption (PREEMPT_RT enabled).
I've run the cyclictest (preemption tests) and then I've realized, that
the HIGH_RESOLUTION_TIMER is not supported on this SoC (yet... I
hope :-) ).
Moreove the low-res counter resolution is 1ms (as shown at
cat /proc/timer_list), which is unacceptable for my application.
I've searched deeper in the specification of this SoC and I've found,
that the Vortex86DX supports two i8254 timers. One is used for DMA,
interrupt controller and disk, but the second seems to be free :-).
root@lukma:~# cat /proc/ioports
0000-001f : dma1
0020-0021 : pic1
0040-0043 : timer0
0050-0053 : timer1 -> for use?
In the i8254 specification it is written, that it is possible to feed
the counter with 10MHz, which would be sufficient.
I've poked around in the 2.6.33.7.2-rt30 source code and I've found,
that mostly the COMEDI "staged driver" supports this chip. It looks
that this driver has some issues and should not be used (it looks
rather old).
I have got following questions:
1. It seems, that i8254/53 is in some kind standard timer chip (like
8237 - DMA chip) and should already HAVE driver (for x86 i386, i486,
i586).
2. It should be possible to use it as a source for HIGH RESOLUTION
TIMER to provide 1/10 MHz = 100 ns resolution. (for me 1us is enough).
I suppose, that "only" proper callbacks should be provided for
HIGH_RESOLUTION_TIMER framework.
3. It should be a common problem for x86 arch clones (in the
embedded world) to provide one or other source for HIGH RESOLUTION
TIMERS to achieve proper schedule timing.
How this problem is tackled on the other x86 variants? There MUST be
either Local Advanced Programmable Interrupt Controller (Local APIC) or
High Precision Event Timer. The vortex86DX SoC has 800MHz clock
freq, so 1us timer precision shall be achievable.
What is the clock source for High Resolution Timers on other processors
with up to i586 ISA?
4. I'd be glad to have an opportunity to solve this problem (I mean to
write a driver for i8254) but I don't want to reinvent the wheel :-)
Any help/ideas/hints are very welcome. I've got some experience with
ARM/PXA cores, so x86 SoC is somewhat new for my real-time, embedded
adventure.
Thanks in advance and regards,
Łukasz Majewski
--
To unsubscribe from this list: send the line "unsubscribe linux-8086" 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: [Vortex86] Vortex86DX SoC - HIGH_RESOLUTION_TIMER
From: Alan Carvalho de Assis @ 2011-04-12 19:59 UTC (permalink / raw)
To: Lukasz Majewski; +Cc: linux-embedded, linux-8086, platform-driver-x86
In-Reply-To: <20110412212739.5658f030@mig>
Hi Lukasz,
On 4/12/11, Lukasz Majewski <majess1982@gmail.com> wrote:
> Hi all,
>
> I'd like to kindly ask for some help with my embedded system.
>
> I'm using the Vortex86DX SoC (it's a x86 32 clone) on my PC104 board.
>
> I've managed to apply and run the 2.6.33.7.2-rt30 RT linux.
>
> It supports the full preemption (PREEMPT_RT enabled).
>
I think you didn't send this email to right mailing list.
Please take a look here:
https://rt.wiki.kernel.org/index.php/Mailinglists
I suggest you to send your question to linux-rt-users@vger.kernel.org
Best Regards,
Alan
^ permalink raw reply
* Re: [Vortex86] Vortex86DX SoC - HIGH_RESOLUTION_TIMER
From: Lukasz Majewski @ 2011-04-12 20:14 UTC (permalink / raw)
To: Alan Carvalho de Assis; +Cc: linux-embedded, linux-8086, platform-driver-x86
In-Reply-To: <BANLkTikFFL1tPgBVwYBA3ocziNfGpW=ycQ@mail.gmail.com>
On Tue, 12 Apr 2011 17:59:47 -0200
Alan Carvalho de Assis <acassis@gmail.com> wrote:
> Hi Lukasz,
>
> On 4/12/11, Lukasz Majewski <majess1982@gmail.com> wrote:
> > Hi all,
> >
> > I'd like to kindly ask for some help with my embedded system.
> >
> > I'm using the Vortex86DX SoC (it's a x86 32 clone) on my PC104
> > board.
> >
> > I've managed to apply and run the 2.6.33.7.2-rt30 RT linux.
> >
> > It supports the full preemption (PREEMPT_RT enabled).
> >
>
> I think you didn't send this email to right mailing list.
>
> Please take a look here:
>
> https://rt.wiki.kernel.org/index.php/Mailinglists
>
> I suggest you to send your question to linux-rt-users@vger.kernel.org
>
> Best Regards,
>
> Alan
Hi Alan,
Thank you for the response.
I've already sent this mail to PREEMPT-RT list.
After that I've realized, that the problem with High Resolution Timer
sources is more broad, and maybe developers from various x86 related
mailing lists can help me a bit.
Best regards,
Lukasz
^ permalink raw reply
* Re: Super Fast Boot of Embedded Linux: 300 ms from boot loader to shell
From: Constantine Shulyupin @ 2011-04-13 14:53 UTC (permalink / raw)
To: Marco Stornelli; +Cc: linux-embedded
In-Reply-To: <4DA331F9.5070902@gmail.com>
Thank you Marco for your feedback. I've added summary of used
optimization method:
• Reduction of kernel and filesystem size
• Kernel features: naked boot, initrd without compression
• Optimization of NAND flash interface in boot loader
• Boot time was measured with utility tstamp
• You can find more detailed list of methods at http://elinux.org/Boot_Time
• Techniques for improving embedded Linux startup time, presentation:
http://www.makelinux.com/emb/fastboot/MontaVista
Actually, detailed implementations of optimization methods are
described in numerous documents.
On Mon, Apr 11, 2011 at 7:53 PM, Marco Stornelli
<marco.stornelli@gmail.com> wrote:
> Il 11/04/2011 16:02, Constantine Shulyupin ha scritto:
>>
>> Hello all,
>>
>> Make Linux Software presents the fastest ever embedded Linux boot for
>> 720 MHz ARM and NAND flash memory. Linux boot time is 300 milliseconds
>> from boot loader to shell. The first goal of the project is to achieve
>> a minimal boot time of a minimal but functional Linux system on common
>> hardware. The second goal is to provide a platform for developing more
>> functional systems with an even minimized boot time.
>>
>
> Hi,
>
> is it a marketing announcement or a presentation of an innovative solution?
> In the first case don't send this kind of email to this mailing list. In the
> second one, could you explain to us your "fast boot" solution or your kernel
> modifications? I can't find any implementation details on the web site.
>
> Marco
>
--
Constantine Shulyupin
http://www.MakeLinux.com/
Embedded Linux Systems,
Device Drivers, TI DaVinci
^ permalink raw reply
* Re: Super Fast Boot of Embedded Linux: 300 ms from boot loader to shell
From: Marco Stornelli @ 2011-04-13 17:27 UTC (permalink / raw)
To: Constantine Shulyupin; +Cc: linux-embedded
In-Reply-To: <BANLkTimLGVfS=wbkmYMRkirsARwFHNYWVw@mail.gmail.com>
Il 13/04/2011 16:53, Constantine Shulyupin ha scritto:
> Thank you Marco for your feedback. I've added summary of used
> optimization method:
> • Reduction of kernel and filesystem size
> • Kernel features: naked boot, initrd without compression
> • Optimization of NAND flash interface in boot loader
> • Boot time was measured with utility tstamp
> • You can find more detailed list of methods at http://elinux.org/Boot_Time
> • Techniques for improving embedded Linux startup time, presentation:
> http://www.makelinux.com/emb/fastboot/MontaVista
>
> Actually, detailed implementations of optimization methods are
> described in numerous documents.
I really know. I was talking about *your* optimization method, but if I
well understand you've just applied a list of well known methods.
^ permalink raw reply
* Re: Super Fast Boot of Embedded Linux: 300 ms from boot loader to shell
From: Matthieu CASTET @ 2011-04-14 7:43 UTC (permalink / raw)
To: Marco Stornelli; +Cc: Constantine Shulyupin, linux-embedded@vger.kernel.org
In-Reply-To: <4DA5DD10.6030305@gmail.com>
Hi,
Marco Stornelli a écrit :
> Il 13/04/2011 16:53, Constantine Shulyupin ha scritto:
>> Thank you Marco for your feedback. I've added summary of used
>> optimization method:
>> • Reduction of kernel and filesystem size
>> • Kernel features: naked boot, initrd without compression
>> • Optimization of NAND flash interface in boot loader
>> • Boot time was measured with utility tstamp
>> • You can find more detailed list of methods at http://elinux.org/Boot_Time
>> • Techniques for improving embedded Linux startup time, presentation:
>> http://www.makelinux.com/emb/fastboot/MontaVista
>>
>> Actually, detailed implementations of optimization methods are
>> described in numerous documents.
>
> I really know. I was talking about *your* optimization method, but if I
> well understand you've just applied a list of well known methods.
Not to say such case are not interesting : loading a linux kernel with only a
serial driver, a ramdisk and a shell as init doesn't reflect reality.
In real product what you want if fast user interaction (sound, mounting big
filesystem with user data, lcd display, ...)
Matthieu
^ permalink raw reply
* Re: [PWM v9 0/3] Implement a generic PWM framework
From: Bill Gatliff @ 2011-04-14 15:12 UTC (permalink / raw)
To: linux-kernel, linux-embedded
In-Reply-To: <1301630392-20793-1-git-send-email-bgat@billgatliff.com>
Guys:
Anyone have any comment on this patch series? Is it finally ready for Linus?
Thanks!
b.g.
On Thu, Mar 31, 2011 at 10:59 PM, Bill Gatliff <bgat@billgatliff.com> wrote:
> This patch series contains the ninth attempt at implementation of a
> generic PWM device interface framework. Think gpiolib, but for
> devices and pseudo-devices that generate pulse-wave-modulated outputs.
>
> Compared to the previous version, this patch series:
>
> * Fixes an unbalanced get_device()/put_device()
>
> A git tree containing these patches may be found here:
>
> http://git.billgatliff.com/pwm.git
>
>
> Regards,
>
>
> b.g.
>
> Bill Gatliff (3):
> PWM: Implement a generic PWM framework
> PWM: GPIO+hrtimer device emulation
> PWM: Atmel PWMC driver
>
> Documentation/pwm.txt | 276 ++++++++++++++++++++++
> MAINTAINERS | 8 +
> drivers/Kconfig | 2 +
> drivers/Makefile | 2 +
> drivers/pwm/Kconfig | 29 +++
> drivers/pwm/Makefile | 7 +
> drivers/pwm/atmel-pwmc.c | 452 ++++++++++++++++++++++++++++++++++++
> drivers/pwm/gpio-pwm.c | 332 ++++++++++++++++++++++++++
> drivers/pwm/pwm.c | 580 ++++++++++++++++++++++++++++++++++++++++++++++
> include/linux/pwm/pwm.h | 143 ++++++++++++
> 10 files changed, 1831 insertions(+), 0 deletions(-)
> create mode 100644 Documentation/pwm.txt
> create mode 100644 drivers/pwm/Kconfig
> create mode 100644 drivers/pwm/Makefile
> create mode 100644 drivers/pwm/atmel-pwmc.c
> create mode 100644 drivers/pwm/gpio-pwm.c
> create mode 100644 drivers/pwm/pwm.c
> create mode 100644 include/linux/pwm/pwm.h
>
> --
> 1.7.4.1
>
>
--
Bill Gatliff
bgat@billgatliff.com
^ permalink raw reply
* RE: [PWM v9 0/3] Implement a generic PWM framework
From: H Hartley Sweeten @ 2011-04-14 18:26 UTC (permalink / raw)
To: Bill Gatliff, linux-kernel@vger.kernel.org,
linux-embedded@vger.kernel.org
In-Reply-To: <BANLkTik6Ftsy2yTA7LBKTUDJkqXPf4JeSA@mail.gmail.com>
On Thursday, April 14, 2011 8:13 AM, Bill Gatliff wrote:
>
> Guys:
>
>
> Anyone have any comment on this patch series? Is it finally ready for Linus?
>
Bill,
I looked at a previous release quite a while ago to see how the ep93xx pwm driver
could use it. I'll try to look at this release in the next couple days and provide
some feedback.
Regards,
Hartley
^ permalink raw reply
* Re: Super Fast Boot of Embedded Linux: 300 ms from boot loader to shell
From: Robert Schwebel @ 2011-04-14 18:52 UTC (permalink / raw)
To: Matthieu CASTET
Cc: Marco Stornelli, Constantine Shulyupin,
linux-embedded@vger.kernel.org
In-Reply-To: <4DA6A5A7.2060701@parrot.com>
On Thu, Apr 14, 2011 at 09:43:35AM +0200, Matthieu CASTET wrote:
> Not to say such case are not interesting : loading a linux kernel with
> only a serial driver, a ramdisk and a shell as init doesn't reflect
> reality.
>
> In real product what you want if fast user interaction (sound,
> mounting big filesystem with user data, lcd display, ...)
Well, it depends on the application. In the automotive box I've shown
the barebox based boot optimizations for in my ELC-E talk, the task is
to get CAN communication running in < 200 ms. For that kind of
application it's useful to be in userspace as fast as possible, just in
order to have one socket-can application running, and everything else
comes later.
rsc
--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
^ permalink raw reply
* Re: [PWM v9 1/3] PWM: Implement a generic PWM framework
From: Grant Likely @ 2011-04-14 19:49 UTC (permalink / raw)
To: Bill Gatliff
Cc: linux-kernel, linux-embedded, Wolfram Sang, Greg Kroah-Hartman,
Kay Sievers
In-Reply-To: <1301630392-20793-2-git-send-email-bgat@billgatliff.com>
Hi Bill,
Comments below...
[cc'ing Greg & Kay regarding the use of device class (comment near the bottom)]
On Thu, Mar 31, 2011 at 9:59 PM, Bill Gatliff <bgat@billgatliff.com> wrote:
> Updates the existing PWM-related functions to support multiple
> and/or hotplugged PWM devices, and adds a sysfs interface.
> Moves the code to drivers/pwm.
>
> For now, this new code can exist alongside the current PWM
> implementations; the existing implementations will be migrated
> to this new framework as time permits. Eventually, the current
> PWM implementation will be deprecated and then expunged.
>
> Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
> ---
> Documentation/pwm.txt | 276 ++++++++++++++++++++++
> MAINTAINERS | 8 +
> drivers/Kconfig | 2 +
> drivers/Makefile | 2 +
> drivers/pwm/Kconfig | 10 +
> drivers/pwm/Makefile | 4 +
> drivers/pwm/pwm.c | 580 +++++++++++++++++++++++++++++++++++++++++++++++
> include/linux/pwm/pwm.h | 140 ++++++++++++
> 8 files changed, 1022 insertions(+), 0 deletions(-)
> create mode 100644 Documentation/pwm.txt
> create mode 100644 drivers/pwm/Kconfig
> create mode 100644 drivers/pwm/Makefile
> create mode 100644 drivers/pwm/pwm.c
> create mode 100644 include/linux/pwm/pwm.h
>
> diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt
> new file mode 100644
> index 0000000..6a0c95d
> --- /dev/null
> +++ b/Documentation/pwm.txt
> @@ -0,0 +1,276 @@
> + Generic PWM Device API
> +
> + February 7, 2011
> + 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.
Good documentation!
> +
> +
> +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 and pwm_config structures to invoke
> +services in PWM peripheral device drivers. Consult
> +drivers/pwm/atmel-pwmc.c for an example driver for the Atmel PWMC
> +peripheral.
> +
> +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_ops structure, which describes
> +the capabilities of the PWM hardware being utilized. 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_ops structure as users
> +begin to configure and utilize the hardware.
> +
> +Many PWM-capable peripherals provide two, three, or more channels of
> +PWM output. The driver author calls pwm_register() once for each
> +channel they wish to be supported by the Generic PWM API.
> +
> +Note that PWM signals can be produced by a variety of peripherals,
> +beyond the true PWM peripherals 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 be accommodated by the Generic PWM Device API framework.
> +
> +The following paragraphs describe the basic functions provided by the
> +Generic PWM API framework. See the kerneldoc in drivers/pwm/pwm.c for
> +the most detailed documentation.
> +
> +
> +Using the API to Generate PWM Signals -- Basic Kernel Functions
> +
> +pwm_request() -- Returns a pwm_device 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_release() 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_release() -- 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.
API looks mostly sane, but more comments on it below at the actual declaration.
> +
> +
> +Using the Sysfs Interface to Generate PWM Signals from Userspace
> +
> +The Generic PWM API provides the following attributes under
> +/sys/class/pwm/<device>/ to allow user applications to control
> +and/or monitor PWM signal generation. Except for the 'export'
> +attribute, all attributes are read-only if the PWM device is not
> +exported to userspace.
> +
> +export (rw) -- write a label to this attribute to request that the PWM
> +device be exported to userspace; returns the length of the label on
> +success (for compatibilty with echo/cat), or -EBUSY if the device is
> +already in use by the kernel or has already been exported to
> +userspace. Read from this attribute to obtain the label of the current
> +PWM device owner, if any.
> +
> +unexport (w) -- write a non-null string to this attribute to release
> +the PWM device; the device then becomes available for reexport and/or
> +requests. Returns -EBUSY if the device is not currently exported,
> +-EINVAL if the device is not currently in use, or the length of the
> +string on success.
> +
> +polarity (rw) -- write an ascii '1' to set active high, or a '0' for
> +active low. Read to obtain the current polarity.
> +
> +period_ns (rw) -- write an ascii decimal number to set the period of
> +the PWM device, in nanoseconds. Value written must not be less than
> +duty_ns or -EINVAL is returned. Read to determine the current period
> +of the PWM device, which might be slightly different than the value
> +requested due to hardware limitations.
> +
> +duty_ns (rw) -- write an ascii decimal number to set the duration of
> +the active portion of the PWM period, in nanoseconds; value written
> +must not exceed period_ns. Read to obtain current duty_ns, which may
> +be slightly different than the value requested due to hardware
> +limitations.
> +
> +tick_hz (r) -- indicates the base tick rate of the underlying
> +hardware, in nanoseconds. Returns '0' if the rate is not yet known,
> +which might be the case if the device has not been requested yet (some
> +drivers don't initialize this value until the hardware is requested,
> +because the value is dynamic).
> +
> +run (rw) -- write '1' to start PWM signal generation, '0' to stop.
> +Read to determine whether the PWM device is running or not.
I'm not convinced that a sysfs interface is the right thing to do.
The more I look at gpio, the more I wish it didn't have the sysfs
mechanism. sysfs has abi stability issues and it's difficult to
ensure it isn't racy when using it to control more than one thing.
I'd prefer the sysfs code to be split into a separate .c file and a
separate patch so it can be reviewed as a separate piece.
Need to be particularly careful about it since this creates a new
user-space ABI which must be preserved for all time. It cannot be
taken lightly.
> +
> +
> +Using the API to Generate PWM Signals -- Advanced Functions
> +
> +pwm_config() -- Passes a pwm_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 by the config structure.
> +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_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, timer
> +handlers, and other interrupt contexts, but must confine their
> +configuration changes to only those that the driver can implement
> +without sleeping. 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.
> +
> +
> +Implementing a PWM Device API Driver -- Functions for Driver Authors
> +
> +
> +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.
> +
> +release -- (optional) Invoked 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.
> +
> +config -- Invoked to change the device configuration, always from a
> +sleep-compatible 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.
> +
> +
> +FAQs and Additional Notes
> +
> +The Atmel PWMC pwm_config() function tries to satisfy the user's
> +configuration request by first invoking pwm_config_nosleep(). If that
> +operation fails, then the PWM peripheral is brought to a synchronized
> +stop, the configuration changes are made, and the device is restarted.
> +
> +The Atmel PWMC's use of pwm_config_nosleep() from pwm_config()
> +minimizes redundant code between the two functions, and relieves the
> +pwm_config() function of the need to explicitly test whether a
> +requested configuration change can be carried out while the PWM device
> +is in its current mode.
> +
> +PWM API driver authors are encouraged to adopt the Atmel PWMC's
> +pwm_config()-vs.-pwm_config_nosleep() strategy in implementations for
> +other devices as well.
> +
> +
> +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/MAINTAINERS b/MAINTAINERS
> index 6b4b9cd..fe55958 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5051,6 +5051,14 @@ S: Maintained
> F: Documentation/video4linux/README.pvrusb2
> F: drivers/media/video/pvrusb2/
>
> +PWM DEVICE API
> +M: Bill Gatliff <bgat@billgatliff.com>
> +L: linux-embedded@vger.kernel.org
> +T: git git://git.billgatliff.com/pwm.git
> +S: Maintained
> +F: Documentation/pwm.txt
> +F: drivers/pwm/
> +
> PXA2xx/PXA3xx SUPPORT
> M: Eric Miao <eric.y.miao@gmail.com>
> M: Russell King <linux@arm.linux.org.uk>
> diff --git a/drivers/Kconfig b/drivers/Kconfig
> index 177c7d1..bc75da5 100644
> --- a/drivers/Kconfig
> +++ b/drivers/Kconfig
> @@ -56,6 +56,8 @@ source "drivers/pps/Kconfig"
>
> source "drivers/gpio/Kconfig"
>
> +source "drivers/pwm/Kconfig"
> +
> source "drivers/w1/Kconfig"
>
> source "drivers/power/Kconfig"
> diff --git a/drivers/Makefile b/drivers/Makefile
> index 3f135b6..132a823 100644
> --- a/drivers/Makefile
> +++ b/drivers/Makefile
> @@ -6,6 +6,8 @@
> #
>
> obj-y += gpio/
> +obj-$(CONFIG_GENERIC_PWM) += pwm/
> +
> obj-$(CONFIG_PCI) += pci/
> obj-$(CONFIG_PARISC) += parisc/
> obj-$(CONFIG_RAPIDIO) += rapidio/
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> new file mode 100644
> index 0000000..bc550f7
> --- /dev/null
> +++ b/drivers/pwm/Kconfig
> @@ -0,0 +1,10 @@
> +#
> +# PWM infrastructure and devices
> +#
> +
> +menuconfig GENERIC_PWM
> + tristate "PWM Support"
> + help
> + Enables PWM device support implemented via a generic
> + framework. If unsure, say N.
> +
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> new file mode 100644
> index 0000000..7baa201
> --- /dev/null
> +++ b/drivers/pwm/Makefile
> @@ -0,0 +1,4 @@
> +#
> +# Makefile for pwm devices
> +#
> +obj-$(CONFIG_GENERIC_PWM) := pwm.o
> diff --git a/drivers/pwm/pwm.c b/drivers/pwm/pwm.c
> new file mode 100644
> index 0000000..9a08fea
> --- /dev/null
> +++ b/drivers/pwm/pwm.c
> @@ -0,0 +1,580 @@
> +/*
> + * PWM API implementation
> + *
> + * Copyright (C) 2011 Bill Gatliff <bgat@billgatliff.com>
> + * Copyright (C) 2011 Arun Murthy <arun.murthy@stericsson.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/kernel.h>
> +#include <linux/init.h>
> +#include <linux/slab.h>
> +#include <linux/device.h>
> +#include <linux/fs.h>
> +#include <linux/sched.h>
> +#include <linux/pwm/pwm.h>
> +
> +static const char *REQUEST_SYSFS = "sysfs";
> +static struct class pwm_class;
> +
> +void pwm_set_drvdata(struct pwm_device *p, void *data)
> +{
> + dev_set_drvdata(&p->dev, data);
> +}
> +EXPORT_SYMBOL(pwm_set_drvdata);
> +
> +void *pwm_get_drvdata(const struct pwm_device *p)
> +{
> + return dev_get_drvdata(&p->dev);
> +}
> +EXPORT_SYMBOL(pwm_get_drvdata);
Are the set/get drvdata hooks really needed? Wouldn't a driver
registering pwm_devices simply wrap the structure with its own private
data? drvdata is typically only needed when the device registration
code is separate from the device driver code.
> +
> +static inline struct pwm_device *to_pwm_device(struct device *dev)
> +{
> + return container_of(dev, struct pwm_device, dev);
> +}
> +
> +static int pwm_match_name(struct device *dev, void *name)
> +{
> + return !strcmp(name, dev_name(dev));
> +}
> +
> +static int __pwm_request(struct pwm_device *p, const char *label)
> +{
> + int ret;
> +
> + if (!try_module_get(p->ops->owner))
> + return -ENODEV;
> +
> + ret = test_and_set_bit(PWM_FLAG_REQUESTED, &p->flags);
> + if (ret) {
> + ret = -EBUSY;
> + goto err_flag_requested;
> + }
> +
> + p->label = label;
> +
> + if (p->ops->request) {
> + ret = p->ops->request(p);
> + if (ret)
> + goto err_request_ops;
> +
> + }
> +
> + return 0;
> +
> +err_request_ops:
> + clear_bit(PWM_FLAG_REQUESTED, &p->flags);
> +
> +err_flag_requested:
> + module_put(p->ops->owner);
> + return ret;
> +}
> +
> +/**
> + * pwm_request - request a PWM device by name
Nit: kerneldoc format has () after the function name.
> + *
> + * @name: name of PWM device
> + * @label: label that identifies requestor
> + *
> + * The @name format is driver-specific, but is typically of the form
> + * "<bus_id>:<chan>". For example, "atmel_pwmc:1" identifies the
> + * second ATMEL PWMC peripheral channel.
> + *
> + * Returns a pointer to the requested PWM device on success, -EINVAL
> + * otherwise.
> + */
> +struct pwm_device *pwm_request(const char *name, const char *label)
> +{
So, the last time I actually was able to look at these patches was
almost a year ago. At that time I had two major concerns. First, I
don't at all think this should be a new subsystem. I know you
disagree, but I strongly think that pwm management is sufficiently
similar to gpio management that it needs to be the same subsystem.
All of the things that need to be done for request, release and
enumeration are essentially the same, and conceptually they I think
they need to be managed within the same namespace.
Second, I deeply dislike the model of matching devices by name
(string). My opinion is it is too easy to break, and it depends on
the having control over how a device is actually going to get named
(which may be true for the current users, but it won't be true for pwm
devices on add-in or hot plugged devices). Basically, in order to be
dynamic-registration friendly, the API needs to assume that any device
names/ids are dynamically assigned by the kernel. A better mechanism
is something like a direct pointer to the pwm instance, or an explicit
reference like a phandle in a device tree (required for powerpc).
It may be fine to have a lookup-by-name mechanism on top of the api,
but it should not be the primary interface for referencing a specific
device.
> + struct device *d;
> + struct pwm_device *p;
> + int ret;
> +
> + d = class_find_device(&pwm_class, NULL, (char*)name, pwm_match_name);
> + if (!d)
> + return ERR_PTR(-EINVAL);
> +
> + p = to_pwm_device(d);
> + ret = __pwm_request(p, label);
> + if (ret) {
> + put_device(d);
> + return ERR_PTR(ret);
> + }
> +
> + return p;
> +}
> +EXPORT_SYMBOL(pwm_request);
EXPORT_SYMBOL_GPL()
> +static struct class pwm_class = {
> + .name = "pwm",
> + .owner = THIS_MODULE,
> + .dev_attrs = pwm_dev_attrs,
> +};
From my understanding, class is deprecated. I believe you should be
using bus_type directly now. Check with Greg and Kay.
> +
> +static void __pwm_release(struct device *dev)
> +{
> + struct pwm_device *p = container_of(dev, struct pwm_device, dev);
> + kfree(p);
> +}
> +
> +/**
> + * pwm_register - registers a PWM device
> + *
> + * @ops: PWM device operations
> + * @parent: reference to parent device, if any
> + * @fmt: printf-style format specifier for device name
> + */
> +struct pwm_device *pwm_register(const struct pwm_device_ops *ops,
> + struct device *parent, const char *fmt, ...)
> +{
> + struct pwm_device *p;
> + int ret;
> + va_list vargs;
> +
> + if (!ops || !ops->config)
> + return ERR_PTR(-EINVAL);
> +
> + p = kzalloc(sizeof(*p), GFP_KERNEL);
> + if (!p)
> + return ERR_PTR(-ENOMEM);
I'd rather see the caller responsible for allocating the pwm_devices
so that it can wrap is with its own structure as needed.
> +
> + p->ops = ops;
> +
> + p->dev.class = &pwm_class;
> + p->dev.parent = parent;
> + p->dev.release = __pwm_release;
> +
> + va_start(vargs, fmt);
> + ret = kobject_set_name_vargs(&p->dev.kobj, fmt, vargs);
> +
> + ret = device_register(&p->dev);
> + if (ret)
> + goto err;
> +
> + return p;
> +
> +err:
> + put_device(&p->dev);
> + return ERR_PTR(ret);
> +}
> +EXPORT_SYMBOL(pwm_register);
> +
> +void pwm_unregister(struct pwm_device *p)
> +{
> + device_unregister(&p->dev);
> +}
> +EXPORT_SYMBOL(pwm_unregister);
> +
> +static int __init pwm_init(void)
> +{
> + return class_register(&pwm_class);
> +}
> +
> +static void __exit pwm_exit(void)
> +{
> + class_unregister(&pwm_class);
> +}
> +
> +postcore_initcall(pwm_init);
> +module_exit(pwm_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Bill Gatliff <bgat@billgatliff.com>");
> +MODULE_DESCRIPTION("Generic PWM device API implementation");
> diff --git a/include/linux/pwm/pwm.h b/include/linux/pwm/pwm.h
> new file mode 100644
> index 0000000..64707a7
> --- /dev/null
> +++ b/include/linux/pwm/pwm.h
> @@ -0,0 +1,140 @@
> +/*
> + * Copyright (C) 2011 Bill Gatliff <bgat@billgatliff.com>
> + * Copyright (C) 2011 Arun Murthy <arun.murth@stericsson.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
> + */
> +#ifndef __LINUX_PWM_H
> +#define __LINUX_PWM_H
> +
> +#include <linux/device.h>
> +
> +enum {
> + PWM_FLAG_REQUESTED = 0,
> + PWM_FLAG_STOP = 1,
> + PWM_FLAG_RUNNING = 2,
> + PWM_FLAG_EXPORTED = 3,
> +};
> +
> +enum {
> + PWM_CONFIG_DUTY_TICKS = 0,
> + PWM_CONFIG_PERIOD_TICKS = 1,
> + PWM_CONFIG_POLARITY = 2,
> + PWM_CONFIG_START = 3,
> + PWM_CONFIG_STOP = 4,
> +};
> +
> +struct pwm_config;
> +struct pwm_device;
> +
> +struct pwm_device_ops {
> + struct module *owner;
> +
> + int (*request) (struct pwm_device *p);
> + void (*release) (struct pwm_device *p);
> + int (*config) (struct pwm_device *p,
> + struct pwm_config *c);
> + int (*config_nosleep) (struct pwm_device *p,
> + struct pwm_config *c);
> + int (*synchronize) (struct pwm_device *p,
> + struct pwm_device *to_p);
> + int (*unsynchronize) (struct pwm_device *p,
> + struct pwm_device *from_p);
> +};
> +
> +/**
> + * struct pwm_config - configuration data for a PWM device
> + *
> + * @config_mask: which fields are valid
> + * @duty_ticks: requested duty cycle, in ticks
> + * @period_ticks: requested period, in ticks
> + * @polarity: active high (1), or active low (0)
> + */
> +struct pwm_config {
> + unsigned long config_mask;
> + unsigned long duty_ticks;
> + unsigned long period_ticks;
> + int polarity;
> +};
> +
> +/**
> + * struct pwm_device - represents a PWM device
> + *
> + * @dev: device model reference
> + * @ops: operations supported by the PWM device
> + * @label: requestor of the PWM device, or NULL
> + * @flags: PWM device state, see FLAG_*
> + * @tick_hz: base tick rate of PWM device, in HZ
> + * @polarity: active high (1), or active low (0)
> + * @period_ticks: PWM device's current period, in ticks
> + * @duty_ticks: duration of PWM device's active cycle, in ticks
> + */
> +struct pwm_device {
> + struct device dev;
> + const struct pwm_device_ops *ops;
> + const char *label;
> + unsigned long flags;
> + unsigned long tick_hz;
> + int polarity;
> + unsigned long period_ticks;
> + unsigned long duty_ticks;
> +};
> +
> +struct pwm_device *pwm_request(const char *name, const char *label);
> +void pwm_release(struct pwm_device *p);
Missing 'extern' in front of all the forward declarations.
> +
> +static inline int pwm_is_requested(const struct pwm_device *p)
> +{
> + return test_bit(PWM_FLAG_REQUESTED, &p->flags);
> +}
> +
> +static inline int pwm_is_running(const struct pwm_device *p)
> +{
> + return test_bit(PWM_FLAG_RUNNING, &p->flags);
> +}
> +
> +static inline int pwm_is_exported(const struct pwm_device *p)
> +{
> + return test_bit(PWM_FLAG_EXPORTED, &p->flags);
> +}
> +
> +struct pwm_device *pwm_register(const struct pwm_device_ops *ops, struct device *parent,
> + const char *fmt, ...);
> +void pwm_unregister(struct pwm_device *p);
> +
> +void pwm_set_drvdata(struct pwm_device *p, void *data);
> +void *pwm_get_drvdata(const struct pwm_device *p);
> +
> +int pwm_set(struct pwm_device *p, unsigned long period_ns,
> + unsigned long duty_ns, int polarity);
> +
> +int pwm_set_period_ns(struct pwm_device *p, unsigned long period_ns);
> +unsigned long pwm_get_period_ns(struct pwm_device *p);
> +
> +int pwm_set_duty_ns(struct pwm_device *p, unsigned long duty_ns);
> +unsigned long pwm_get_duty_ns(struct pwm_device *p);
> +
> +int pwm_set_polarity(struct pwm_device *p, int polarity);
> +
> +int pwm_start(struct pwm_device *p);
> +int pwm_stop(struct pwm_device *p);
> +
> +int pwm_config_nosleep(struct pwm_device *p, struct pwm_config *c);
> +int pwm_config(struct pwm_device *p, struct pwm_config *c);
> +
> +int pwm_synchronize(struct pwm_device *p, struct pwm_device *to_p);
> +int pwm_unsynchronize(struct pwm_device *p, struct pwm_device *from_p);
> +
> +#endif
> --
> 1.7.4.1
>
> --
> 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
>
--
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.
^ permalink raw reply
* Re: [PWM v9 1/3] PWM: Implement a generic PWM framework
From: Greg KH @ 2011-04-14 19:58 UTC (permalink / raw)
To: Grant Likely
Cc: Bill Gatliff, linux-kernel, linux-embedded, Wolfram Sang,
Kay Sievers
In-Reply-To: <BANLkTi=1Nh+EcZw_U6U8ZS2yVyqmCtuAcQ@mail.gmail.com>
On Thu, Apr 14, 2011 at 01:49:27PM -0600, Grant Likely wrote:
> > +static struct class pwm_class = {
> > + .name = "pwm",
> > + .owner = THIS_MODULE,
> > + .dev_attrs = pwm_dev_attrs,
> > +};
>
> >From my understanding, class is deprecated. I believe you should be
> using bus_type directly now. Check with Greg and Kay.
Yes, that is correct, please don't create new classes if at all
possible. And this code looks like it is fine to use a bus_type.
thanks,
greg k-h
^ permalink raw reply
* [PATCH 0/4] Speed up the symbols' resolution process V3
From: Alessio Igor Bogani @ 2011-04-15 15:24 UTC (permalink / raw)
To: Rusty Russell, Tim Abbott, Anders Kaseorg, Jason Wessel, Tim Bird
Cc: LKML, Linux Embedded, Alessio Igor Bogani
The intent of this patch is to speed up the symbols resolution process.
This objective is achieved by sorting all ksymtab* and kcrctab* symbols
(those which reside both in the kernel and in the modules) and thus use the
fast binary search.
To avoid adding lots of code for symbols sorting I rely on the linker which can
easily do the job thanks to a little trick. The trick isn't really beautiful to
see but permits minimal changes to the code and build process. Indeed the patch
is very simple and short.
In the first place I changed the code for place every symbol in a different
section (for example: "___ksymtab" sec "__" #sym) at compile time (this the
above mentioned trick!). Thus I request to the linker to sort and merge all
these sections into the appropriate ones (for example: "__ksymtab") at link
time using the linker scripts. Once all symbols are sorted we can use binary
search instead of the linear one.
I'm fairly sure that this is a good speed improvement even though I haven't
made any comprehensive benchmarking (but follow a simple one). In any case
I would be very happy to receive suggestions about how made it. Collaterally,
the boot time should be reduced also (proportionally to the number of modules
and symbols nvolved at boot stage).
I hope that you find that interesting!
This work was supported by a hardware donation from the CE Linux Forum.
Thanks to Ian Lance Taylor for help about how the linker works.
Changes since V2:
*) Fix a bug in each_symbol() semantics by Anders Kaseorg
*) Split the work in three patches as requested by Rusty Russell
*) Add a generic binary search implementation made by Tim Abbott
*) Remove CONFIG_SYMBOLS_BSEARCH kernel option
Changes since V1:
*) Merge all patches into only one
*) Remove few useless things
*) Introduce CONFIG_SYMBOLS_BSEARCH kernel option
Alessio Igor Bogani (3):
module: Split the find_symbol_in_section function
module: Sort exported symbols
module: Use the binary search for symbols resolution
Tim Abbott (1):
lib: Add generic binary search function to the kernel.
include/asm-generic/vmlinux.lds.h | 20 +++++++-------
include/linux/bsearch.h | 9 ++++++
include/linux/module.h | 7 +++--
kernel/module.c | 37 ++++++++++++++++---------
lib/Makefile | 3 +-
lib/bsearch.c | 53 +++++++++++++++++++++++++++++++++++++
scripts/module-common.lds | 11 +++++++
7 files changed, 113 insertions(+), 27 deletions(-)
create mode 100644 include/linux/bsearch.h
create mode 100644 lib/bsearch.c
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox