Embedded Linux development
 help / color / mirror / Atom feed
* Re: Getting physical addresses of mmap'd pages from userspace
From: Robert Schwebel @ 2008-10-13  7:31 UTC (permalink / raw)
  To: Thomas Petazzoni
  Cc: Tom Cooksey, linux-embedded mailing list, Denis Olvier Kropp
In-Reply-To: <20081013092815.1792708d@surf>

On Mon, Oct 13, 2008 at 09:28:15AM +0200, Thomas Petazzoni wrote:
> > We are working on DirectFB [1] support for the PowerVR MBX & SGX
> > chips these days, so I know the problem :) Our hardware platform is
> > i.MX31 with the MBX, MPC5121e also with MBX (both freescale) and a
> > TI OMAP with the SGX.
>
> This is interesting. Is this work available somewhere? Is it
> open-source, or does it rely on proprietary drivers?

It will be available when we have something which is worth being
reviewed in public. As there is currently no possibility to get any
documentation about the cores without NDAs, an open *driver* is IMHO not
possible, so we'll have to rely on the proprietary drivers Freescale
deliver. Bad, but I don't see another chance by now.

rsc
-- 
 Dipl.-Ing. Robert Schwebel | http://www.pengutronix.de
 Pengutronix - Linux Solutions for Science and Industry
   Handelsregister:  Amtsgericht Hildesheim, HRA 2686
     Hannoversche Str. 2, 31134 Hildesheim, Germany
   Phone: +49-5121-206917-0 |  Fax: +49-5121-206917-9

^ permalink raw reply

* Re: [RFC 0/6] Proposal for a Generic PWM Device API
From: Geert Uytterhoeven @ 2008-10-13  7:40 UTC (permalink / raw)
  To: Bill Gatliff; +Cc: Paul Mundt, Linux/PPC Development, linux-embedded
In-Reply-To: <48EFB025.6050808@billgatliff.com>

[-- Attachment #1: Type: TEXT/PLAIN, Size: 1411 bytes --]

On Fri, 10 Oct 2008, Bill Gatliff wrote:
> Paul Mundt wrote:
> > Your first version should have been to linux-embedded and linux-kernel.
> > If you want to alert the linux-arm-kernel people to the fact that a
> > discussion is going on in this area, then feel free to post a
> > notification to the list with references to the relevant lists. There is
> > no reason why public lists should have to dig in to private archives to
> > try and play catch up.
> 
> I'm not asking anyone to do that.  Just review the patches posted to the list of
> your choice.  Or, don't review them.  Up to you.
> 
> My next update will be the one where I formally request a review with intent to
> merge into mainline.  That one will go to linux-embedded only, with
> notifications sent elsewhere as indicated per community request.  I don't have a
> problem with that.  I can't change history, but I'm doing what you are asking of
> me otherwise.

For a formally request for review, you do want to CC lkml.

With kind regards,

Geert Uytterhoeven
Software Architect

Sony Techsoft Centre Europe
The Corporate Village · Da Vincilaan 7-D1 · B-1935 Zaventem · Belgium

Phone:    +32 (0)2 700 8453
Fax:      +32 (0)2 700 8622
E-mail:   Geert.Uytterhoeven@sonycom.com
Internet: http://www.sony-europe.com/

A division of Sony Europe (Belgium) N.V.
VAT BE 0413.825.160 · RPR Brussels
Fortis · BIC GEBABEBB · IBAN BE41293037680010

^ permalink raw reply

* Re: Getting physical addresses of mmap'd pages from userspace
From: Gilad Ben-Yossef @ 2008-10-13  9:37 UTC (permalink / raw)
  To: Tom Cooksey; +Cc: Robert Schwebel, linux-embedded mailing list
In-Reply-To: <200810130833.26399.thomas.cooksey@trolltech.com>

Tom Cooksey wrote:

> ...
>  This library allows us to direct the graphics hardware to render to a specific physical memory region. The problem is that there's no way to find out what the physical addresses are which we need pass to the graphics hardware (via the user-space library). Allthough the library allows us to allocate emory, what I want to do is then blit that memory in a different process. So a client process renders into an off-screen buffer and the server process blits that buffer to the framebuffer. By allowing the server process to do the blit rather than the client process, we can get semi-transparent GL windows. 
>
> The synchronisation we can do, it's the memory allocation I'm struggling with. If we ask the library to allocate the memory for us, we don't get the physical address to pass to the server process. Instead, we need to allocate memory ourselves and pass the physical address to the library. But like I say, I can't find a way to get the physical address from the kernel.
>
>   
You lost me on why you can't have the library allocate the memory - I 
understand the library needs the physical address to render into, but 
why does your server needs the physical address to blit to the frame 
buffer? I assume it access the frame buffer in host memory so needs the 
virtual address, not the physical one...

Anyways, ignoring that, it's pretty trivial to write  a character driver 
with an IOCTL that will get an mlocked virtual address of the processes, 
call virt_to_phys on it and return the result. Yes, it's an ugly as hell 
solution, but then again so is the problem (proprietary libraries etc.)

You do realize that the mlocked memory isn't contiguous in physical 
memory, right? unless your graphics engine has something like AGP GART 
or an IOMMU I think you might find that problematic.

Gilad


-- 
Gilad Ben-Yossef 
Chief Coffee Drinker

Codefidence Ltd.
The code is free, your time isn't.(TM)

Web:    http://codefidence.com
Email:  gilad@codefidence.com
Office: +972-8-9316883 ext. 201
Fax:    +972-8-9316885
Mobile: +972-52-8260388

	The Doctor: Don't worry, Reinette, just a nightmare. 
	Everyone has nightmares. Even monsters from under the 
	bed have nightmares, don't you, monster?
	Reinette: What do monsters have nightmares about?
	The Doctor: Me! 

^ permalink raw reply

* Re: Getting physical addresses of mmap'd pages from userspace
From: Tom Cooksey @ 2008-10-13  9:38 UTC (permalink / raw)
  To: Gilad Ben-Yossef; +Cc: Robert Schwebel, linux-embedded mailing list
In-Reply-To: <48F31155.6090603@codefidence.com>

On Monday 13 October 2008 11:13:57 Gilad Ben-Yossef wrote:
> Tom Cooksey wrote:
> 
> 
> > This library allows us to direct the graphics hardware to render to a specific physical memory region. The problem is that there's no way to find out what the physical addresses are which we need pass to the graphics hardware (via the user-space library). Allthough the library allows us to allocate emory, what I want to do is then blit that memory in a different process. So a client process renders into an off-screen buffer and the server process blits that buffer to the framebuffer. By allowing the server process to do the blit rather than the client process, we can get semi-transparent GL windows. 
> >
> > The synchronisation we can do, it's the memory allocation I'm struggling with. If we ask the library to allocate the memory for us, we don't get the physical address to pass to the server process. Instead, we need to allocate memory ourselves and pass the physical address to the library. But like I say, I can't find a way to get the physical address from the kernel.
> >
> >   
> You lost me on why you can't have the library allocate the memory - I 
> understand the library needs the physical address to render into, but 
> why does your server needs the physical address to blit to the frame 
> buffer? I assume it access the frame buffer in host memory so needs the 
> virtual address, not the physical one...

The reason is that the hardware blitter is exposed via the user-space library,
but because the blit is done by hardware, it too needs the physical address of
both the source and the destination of the blit. The destination is easy - the
library provides an API to get the address of the framebuffer. It's the source
that's the problem.

> Anyways, ignoring that, it's pretty trivial to write  a character driver 
> with an IOCTL that will get an mlocked virtual address of the processes, 
> call virt_to_phys on it and return the result. Yes, it's an ugly as hell 
> solution, but then again so is the problem (proprietary libraries etc.)

I realise this is possible, but I'm trying to find a solution which doesn't
require me to write a kernel driver. If I can't find a solution, I will follow
your suggestion of using virt_to_phys - thanks!

> You do realize do that the mlocked memory isn't contiguous in physical 
> memory, right? unless your graphics engine has something like AGP GART 
> or an IOMMU I think you might find that problematic.

I had assumed that. Fortunatly the user-space library's API takes an array of
physical page addresses. The hardware actually has it's own MMU (I guess
that's an IOMMU?) with enough entries to address quite a lot of memory. I'm
assuming the library programs the GPU's MMU with the addresses I pass in.

I also think that I need to be careful with caching. The CPU (OMAP3) has both
an L1 and L2 cache. I doubt there's any cache coherency logic in the hardware
to make sure the CPU's cache is flushed before the GPU startsreading from it.

The last thing I need is to write to the memory I want to blit (using a 
software renderer running on the CPU), only to find the data I've written is
sitting around in the CPU cache and has not been flushed to RAM (in which case
I'll get garbage on the screen).

I guess I'm going to need a way to not only get the physical address of the
pages, but also set the cache policy or (ideally) have a way to flush the pages
to RAM.



Cheers,

Tom


^ permalink raw reply

* Re: Getting physical addresses of mmap'd pages from userspace
From: Bill Gatliff @ 2008-10-13 12:48 UTC (permalink / raw)
  To: Tom Cooksey; +Cc: Robert Schwebel, linux-embedded mailing list
In-Reply-To: <200810130833.26399.thomas.cooksey@trolltech.com>

Tom Cooksey wrote:
> On Friday 10 October 2008 21:12:13 Robert Schwebel wrote:
>> On Fri, Oct 10, 2008 at 06:15:05PM +0200, Tom Cooksey wrote:
>>> Is there any way to get the physical address of mlock()'d memory from
>>> userspace?
>> What do you want to do? I don't really see a reason to do such strange
>> things any more these days.
> 
> It's quite an annoying problem actually. Basically I have binary drivers for Imagination Technologies's PowerVR graphics drivers. We have tried very hard to get the source code for these drivers and have failed. Eventually ImgTec allowed us to sign an NDA to get the headers for one of their user-space libraries. This library allows us to direct the graphics hardware to render to a specific physical memory region. The problem is that there's no way to find out what the physical addresses are which we need pass to the graphics hardware (via the user-space library). Allthough the library allows us to allocate emory, what I want to do is then blit that memory in a different process. So a client process renders into an off-screen buffer and the server process blits that buffer to the framebuf
 fer. By allowing the server process to do the blit rather than the client process, we can get semi-transparent GL windows. 
> 
> The synchronisation we can do, it's the memory allocation I'm struggling with. If we ask the library to allocate the memory for us, we don't get the physical address to pass to the server process. Instead, we need to allocate memory ourselves and pass the physical address to the library. But like I say, I can't find a way to get the physical address from the kernel.
> 
> I realise getting round closed-source drivers isn't exactly encoraged, but sadly, we really have no choice. :-(

Not that this would help, but what if the blitting process was working with a
shared memory area with the, er, main process?  Could the allocation be done in
the library, then?

Reading your post carefully, it sounds like either the library in question is
designed to run in kernel space (where you can get the physical address), isn't
a Linux library, or there's some more code somewhere that can do the address
translation--- you said that "This library allows us to direct the graphics
hardware to render to a specific physical memory region".  How are other users
of the library getting the parameters that the library needs?

What about writing a hacky little module that just does virtual-to-physical
translation, and returns the result?

Ugh, I think I need to shower.  :)



b.g.
-- 
Bill Gatliff
bgat@billgatliff.com

^ permalink raw reply

* Re: Getting physical addresses of mmap'd pages from userspace
From: Bill Gatliff @ 2008-10-13 12:50 UTC (permalink / raw)
  To: Robert Schwebel
  Cc: Tom Cooksey, linux-embedded mailing list, Denis Olvier Kropp
In-Reply-To: <20081013070017.GH9852@pengutronix.de>

Robert Schwebel wrote:
> In reality, there are no real alternatives for accelerates chips.

Would the Silicon Motion SM50x chips qualify as an alternative?

They can do the blitting, at least.  No OpenGL, tho.  So, I guess it depends on
your definition of "accelerated"...


b.g.
-- 
Bill Gatliff
bgat@billgatliff.com

^ permalink raw reply

* Re: Getting physical addresses of mmap'd pages from userspace
From: Robert Schwebel @ 2008-10-13 13:23 UTC (permalink / raw)
  To: Bill Gatliff; +Cc: Tom Cooksey, linux-embedded mailing list, Denis Olvier Kropp
In-Reply-To: <48F34400.4000404@billgatliff.com>

On Mon, Oct 13, 2008 at 07:50:08AM -0500, Bill Gatliff wrote:
> Robert Schwebel wrote:
> > In reality, there are no real alternatives for accelerates chips.
>
> Would the Silicon Motion SM50x chips qualify as an alternative?
>
> They can do the blitting, at least.  No OpenGL, tho.  So, I guess it
> depends on your definition of "accelerated"...

Yup, from our perspective it would be fine. Unfortunately, the hardware
people in our project had (subjective, imho) concerns.

rsc
-- 
 Dipl.-Ing. Robert Schwebel | http://www.pengutronix.de
 Pengutronix - Linux Solutions for Science and Industry
   Handelsregister:  Amtsgericht Hildesheim, HRA 2686
     Hannoversche Str. 2, 31134 Hildesheim, Germany
   Phone: +49-5121-206917-0 |  Fax: +49-5121-206917-9

^ permalink raw reply

* Re: Getting physical addresses of mmap'd pages from userspace
From: Tom Cooksey @ 2008-10-13 14:45 UTC (permalink / raw)
  To: Bill Gatliff; +Cc: Robert Schwebel, linux-embedded mailing list
In-Reply-To: <48F34386.8040205@billgatliff.com>

On Monday 13 October 2008 14:48:06 Bill Gatliff wrote:
> Tom Cooksey wrote:
> > On Friday 10 October 2008 21:12:13 Robert Schwebel wrote:
> >> On Fri, Oct 10, 2008 at 06:15:05PM +0200, Tom Cooksey wrote:
> >>> Is there any way to get the physical address of mlock()'d memory from
> >>> userspace?
> >> What do you want to do? I don't really see a reason to do such strange
> >> things any more these days.
> > 
> > It's quite an annoying problem actually. Basically I have binary drivers for Imagination Technologies's PowerVR graphics drivers. We have tried very hard to get the source code for these drivers and have failed. Eventually ImgTec allowed us to sign an NDA to get the headers for one of their user-space libraries. This library allows us to direct the graphics hardware to render to a specific physical memory region. The problem is that there's no way to find out what the physical addresses are which we need pass to the graphics hardware (via the user-space library). Allthough the library allows us to allocate emory, what I want to do is then blit that memory in a different process. So a client process renders into an off-screen buffer and the server process blits that buffer to the frameb
 uffer. By allowing the server process to do the blit rather than the client process, we can get semi-transparent GL windows. 
> > 
> > The synchronisation we can do, it's the memory allocation I'm struggling with. If we ask the library to allocate the memory for us, we don't get the physical address to pass to the server process. Instead, we need to allocate memory ourselves and pass the physical address to the library. But like I say, I can't find a way to get the physical address from the kernel.
> > 
> > I realise getting round closed-source drivers isn't exactly encoraged, but sadly, we really have no choice. :-(
> 
> Not that this would help, but what if the blitting process was working with a
> shared memory area with the, er, main process?  Could the allocation be done in
> the library, then?
> 
> Reading your post carefully, it sounds like either the library in question is
> designed to run in kernel space (where you can get the physical address), isn't
> a Linux library, or there's some more code somewhere that can do the address
> translation--- you said that "This library allows us to direct the graphics
> hardware to render to a specific physical memory region".  How are other users
> of the library getting the parameters that the library needs?

The other user is more closed source OpenGL library code. The problem is that
the drivers are designed to blit from the client process, where the library
does the allocation & the blits (which means opaque-only blits). If we want
transparent windows or fancy window composition effects we have to blit from a
central server process and have clients render into off-screen buffers.

Also, you're right - The library seems to not be Linux-specific but targeted at
other OSes (Which ones I can't say without breaking the NDA I had to sign!).

> What about writing a hacky little module that just does virtual-to-physical
> translation, and returns the result?
> 
> Ugh, I think I need to shower.  :)

Yup, I think that's what I'm going to end up having to do. Even if I can get
the physical address, I need some way to flush the CPU cache to RAM before I
ask the GPU to blit. I doubt there's any way to do that from userspace. :-( I'm
not even sure of the kernel API to do it. Any ideas?


Cheers,

Tom

^ permalink raw reply

* Re: Getting physical addresses of mmap'd pages from userspace
From: Daniel THOMPSON @ 2008-10-13 15:09 UTC (permalink / raw)
  To: Tom Cooksey; +Cc: Bill Gatliff, Robert Schwebel, linux-embedded mailing list
In-Reply-To: <200810131645.32805.thomas.cooksey@trolltech.com>

Tom Cooksey wrote:
> Yup, I think that's what I'm going to end up having to do. Even if I can get
> the physical address, I need some way to flush the CPU cache to RAM before I
> ask the GPU to blit. I doubt there's any way to do that from userspace. :-( I'm
> not even sure of the kernel API to do it. Any ideas?

MIPS has it: http://www.linux-mips.org/wiki/Cacheflush_Syscall
and so does SH. I'm afraid I don't know about ARM though.

-- 
Daniel Thompson (STMicroelectronics) <daniel.thompson@st.com>
1000 Aztec West, Almondsbury, Bristol, BS32 4SQ. 01454 462659

If a car is a horseless carriage then is a motorcycle a horseless horse?

^ permalink raw reply

* Re: Getting physical addresses of mmap'd pages from userspace
From: George G. Davis @ 2008-10-13 15:58 UTC (permalink / raw)
  To: Robert Schwebel
  Cc: Bill Gatliff, Tom Cooksey, linux-embedded mailing list,
	Denis Olvier Kropp
In-Reply-To: <20081013132303.GU9852@pengutronix.de>

On Mon, Oct 13, 2008 at 03:23:03PM +0200, Robert Schwebel wrote:
> On Mon, Oct 13, 2008 at 07:50:08AM -0500, Bill Gatliff wrote:
> > Robert Schwebel wrote:
> > > In reality, there are no real alternatives for accelerates chips.
> >
> > Would the Silicon Motion SM50x chips qualify as an alternative?
> >
> > They can do the blitting, at least.  No OpenGL, tho.  So, I guess it
> > depends on your definition of "accelerated"...
> 
> Yup, from our perspective it would be fine. Unfortunately, the hardware
> people in our project had (subjective, imho) concerns.

It comes down to decisions of bill-of-materials and performance, the
MBX/SGX options mentioned already are part of the SoCs and have
high bandwidth connections to the processor.  External options increase
cost and won't match bandwidth of on chip options, but that's just my
ignorant opinion w/o looking at the low-level detail comparisons.
Unless you intend to encourage the SoC vendors to use the SM50x
instead?  : )  Of course, that won't help for the current and planned
generation(s) of devices...

--
Regards,
George
> 
> rsc
> -- 
>  Dipl.-Ing. Robert Schwebel | http://www.pengutronix.de
>  Pengutronix - Linux Solutions for Science and Industry
>    Handelsregister:  Amtsgericht Hildesheim, HRA 2686
>      Hannoversche Str. 2, 31134 Hildesheim, Germany
>    Phone: +49-5121-206917-0 |  Fax: +49-5121-206917-9
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-embedded" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* Re: Getting physical addresses of mmap'd pages from userspace
From: Robert Schwebel @ 2008-10-13 16:09 UTC (permalink / raw)
  To: George G. Davis
  Cc: Bill Gatliff, Tom Cooksey, linux-embedded mailing list,
	Denis Olvier Kropp
In-Reply-To: <20081013155820.GE2556@mvista.com>

On Mon, Oct 13, 2008 at 11:58:20AM -0400, George G. Davis wrote:
> It comes down to decisions of bill-of-materials and performance, the
> MBX/SGX options mentioned already are part of the SoCs and have high
> bandwidth connections to the processor. External options increase cost
> and won't match bandwidth of on chip options, but that's just my
> ignorant opinion w/o looking at the low-level detail comparisons.
> Unless you intend to encourage the SoC vendors to use the SM50x
> instead?  : )  Of course, that won't help for the current and planned
> generation(s) of devices...

The BoM argument is probably true for high volume consumer or automotive
use cases. For industrial usage, long term maintainability and thus
availability of documentation and code is usually a more important
argument.

Just my 0.02 ct. I'm just a poor open source guy :)

rsc
-- 
 Dipl.-Ing. Robert Schwebel | http://www.pengutronix.de
 Pengutronix - Linux Solutions for Science and Industry
   Handelsregister:  Amtsgericht Hildesheim, HRA 2686
     Hannoversche Str. 2, 31134 Hildesheim, Germany
   Phone: +49-5121-206917-0 |  Fax: +49-5121-206917-9

^ permalink raw reply

* Re: Getting physical addresses of mmap'd pages from userspace
From: George G. Davis @ 2008-10-13 17:21 UTC (permalink / raw)
  To: Daniel THOMPSON
  Cc: Tom Cooksey, Bill Gatliff, Robert Schwebel,
	linux-embedded mailing list
In-Reply-To: <48F364B0.2060407@st.com>

On Mon, Oct 13, 2008 at 04:09:36PM +0100, Daniel THOMPSON wrote:
> Tom Cooksey wrote:
> > Yup, I think that's what I'm going to end up having to do. Even if I can get
> > the physical address, I need some way to flush the CPU cache to RAM before I
> > ask the GPU to blit. I doubt there's any way to do that from userspace. :-( I'm
> > not even sure of the kernel API to do it. Any ideas?
> 
> MIPS has it: http://www.linux-mips.org/wiki/Cacheflush_Syscall
> and so does SH. I'm afraid I don't know about ARM though.

ARM also implements sys_cacheflush(start, end /* exclusive */, 0) but
does not provide separate methods for I or D cache as in the MIPS docs
above, ARM sys_cacheflush'es both I+D caches.

--
Regards,
George
> 
> -- 
> Daniel Thompson (STMicroelectronics) <daniel.thompson@st.com>
> 1000 Aztec West, Almondsbury, Bristol, BS32 4SQ. 01454 462659
> 
> If a car is a horseless carriage then is a motorcycle a horseless horse?
> --
> To unsubscribe from this list: send the line "unsubscribe linux-embedded" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* Re: Getting physical addresses of mmap'd pages from userspace
From: Chris @ 2008-10-13 17:29 UTC (permalink / raw)
  To: Tom Cooksey; +Cc: Robert Schwebel, linux-embedded mailing list
In-Reply-To: <200810130833.26399.thomas.cooksey@trolltech.com>

Tom Cooksey wrote:
> On Friday 10 October 2008 21:12:13 Robert Schwebel wrote:
>> On Fri, Oct 10, 2008 at 06:15:05PM +0200, Tom Cooksey wrote:
>>> Is there any way to get the physical address of mlock()'d memory from
>>> userspace?
>> What do you want to do? I don't really see a reason to do such strange
>> things any more these days.
> 
> It's quite an annoying problem actually. Basically I have binary drivers for Imagination Technologies's PowerVR graphics drivers. We have tried very hard to get the source code for these drivers and have failed. Eventually ImgTec allowed us to sign an NDA to get the headers for one of their user-space libraries. This library allows us to direct the graphics hardware to render to a specific physical memory region. The problem is that there's no way to find out what the physical addresses are which we need pass to the graphics hardware (via the user-space library). Allthough the library allows us to allocate emory, what I want to do is then blit that memory in a different process. So a client process renders into an off-screen buffer and the server process blits that buffer to the framebuf
 fer. By allowing the server process to do the blit rather than the client process, we can get semi-transparent GL windows. 
> 
> The synchronisation we can do, it's the memory allocation I'm struggling with. If we ask the library to allocate the memory for us, we don't get the physical address to pass to the server process. Instead, we need to allocate memory ourselves and pass the physical address to the library. But like I say, I can't find a way to get the physical address from the kernel.
> 
> I realise getting round closed-source drivers isn't exactly encoraged, but sadly, we really have no choice. :-(
> 
> 
> 
> Cheers,
> 
> Tom
> --
> 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
> 

Hi,

Could you reserve memory for the intermediate blit buffer at the end of 
system memory by passing a mem=xxx in the kernel command line and then 
use mmap /dev/mem to read it back? One advantage is that mmapping 
/dev/mem disables the processor cache on those pages (by calling
pgprot_noncached(), see drivers/char/mem.c).

Or, if you are going to write a small driver anyhoe, why not have it 
allocate memory using __get_free_pages and pass the physical address 
back via an ioctl or similar. Once again you can mmap /dev/mem to read 
the buffer from user space.

HTH,
Chris.

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

^ permalink raw reply

* Re: Getting physical addresses of mmap'd pages from userspace
From: Tom Cooksey @ 2008-10-14  6:36 UTC (permalink / raw)
  To: linux-embedded mailing list
In-Reply-To: <20081013160927.GC9852@pengutronix.de>

On Monday 13 October 2008 18:09:27 Robert Schwebel wrote:
> On Mon, Oct 13, 2008 at 11:58:20AM -0400, George G. Davis wrote:
> > It comes down to decisions of bill-of-materials and performance, the
> > MBX/SGX options mentioned already are part of the SoCs and have high
> > bandwidth connections to the processor. External options increase cost
> > and won't match bandwidth of on chip options, but that's just my
> > ignorant opinion w/o looking at the low-level detail comparisons.
> > Unless you intend to encourage the SoC vendors to use the SM50x
> > instead?  : )  Of course, that won't help for the current and planned
> > generation(s) of devices...
> 
> The BoM argument is probably true for high volume consumer or automotive
> use cases. For industrial usage, long term maintainability and thus
> availability of documentation and code is usually a more important
> argument.
> 
> Just my 0.02 ct. I'm just a poor open source guy :)

<RANT>
What I don't understand is that I'm trying to do some pretty interesting & cool
stuff with their processors (of course I would say that!), which will probably
help them sell more units. Why then do they make it so difficult to work with
them? It feels like they're shooting themselves in the foot. Madness.
</RANT>

Cheers,

Tom

^ permalink raw reply

* Re: Getting physical addresses of mmap'd pages from userspace
From: Tom Cooksey @ 2008-10-14  6:46 UTC (permalink / raw)
  To: linux-embedded mailing list
In-Reply-To: <48F38592.4030207@2net.co.uk>

On Monday 13 October 2008 19:29:54 Chris wrote:
> Could you reserve memory for the intermediate blit buffer at the end of 
> system memory by passing a mem=xxx in the kernel command line and then 
> use mmap /dev/mem to read it back? One advantage is that mmapping 
> /dev/mem disables the processor cache on those pages (by calling
> pgprot_noncached(), see drivers/char/mem.c).

I'm not sure if disabling the processor cache completely is what I want to do.
Although if I think about it, I doubt I'll end up using our software rasterizer
too much. In which case, I guess it's only the GPU which will ever be rendering
so the cache problem goes away.

I think I might try a little test application and see if I can get away with
the mem= trick. It's just feels a shame to "reserve" memory & potentially not
use it when it's such a precious resource. I guess it's that or write a kernel
module.


Thanks for everyone's help.



Cheers,

Tom

^ permalink raw reply

* RE: Getting physical addresses of mmap'd pages from userspace
From: Daniel J Laird @ 2008-10-14  7:31 UTC (permalink / raw)
  To: Tom Cooksey, Robert Schwebel; +Cc: linux-embedded mailing list
In-Reply-To: <200810130833.26399.thomas.cooksey@trolltech.com>

We have a 2D chip on board with our silicon.
It too had a driver in kernel space and a queue in user space that allowed us to post jobs to it like Blit/Fill etc.

We run DirectFB on the platform and we too needed to get Physical addresses etc.

In the end what we did was use Linux FB device to allocate our memory.

We allocate more than we need in the FB device when it is created.
The Double buffered FB comes first and what is left over we use as scratch memory.
This allows us to create surfaces etc in this 'scratch' memory.
When we need the physical address we use the offset from FB base in virtual memory (user space) and add that the physical base address of FB device.
This then allows us to post the job to the 2D hardware.

This approach has worked on 2 different chips now.  Maybe this might help for you?
Daniel Laird

-----Original Message-----
From: linux-embedded-owner@vger.kernel.org [mailto:linux-embedded-owner@vger.kernel.org] On Behalf Of Tom Cooksey
Sent: 2008 Oct 13 07:33
To: Robert Schwebel
Cc: linux-embedded mailing list
Subject: Re: Getting physical addresses of mmap'd pages from userspace

On Friday 10 October 2008 21:12:13 Robert Schwebel wrote:
> On Fri, Oct 10, 2008 at 06:15:05PM +0200, Tom Cooksey wrote:
> > Is there any way to get the physical address of mlock()'d memory from
> > userspace?
>
> What do you want to do? I don't really see a reason to do such strange
> things any more these days.

It's quite an annoying problem actually. Basically I have binary drivers for Imagination Technologies's PowerVR graphics drivers. We have tried very hard to get the source code for these drivers and have failed. Eventually ImgTec allowed us to sign an NDA to get the headers for one of their user-space libraries. This library allows us to direct the graphics hardware to render to a specific physical memory region. The problem is that there's no way to find out what the physical addresses are which we need pass to the graphics hardware (via the user-space library). Allthough the library allows us to allocate emory, what I want to do is then blit that memory in a different process. So a client process renders into an off-screen buffer and the server process blits that buffer to the framebuffe
 r. By allowing the server process to do the blit rather than the client process, we can get semi-transparent GL windows.

The synchronisation we can do, it's the memory allocation I'm struggling with. If we ask the library to allocate the memory for us, we don't get the physical address to pass to the server process. Instead, we need to allocate memory ourselves and pass the physical address to the library. But like I say, I can't find a way to get the physical address from the kernel.

I realise getting round closed-source drivers isn't exactly encoraged, but sadly, we really have no choice. :-(



Cheers,

Tom

^ permalink raw reply

* Re: Getting physical addresses of mmap'd pages from userspace
From: Tom Cooksey @ 2008-10-14  9:03 UTC (permalink / raw)
  To: Daniel J Laird; +Cc: Robert Schwebel, linux-embedded mailing list
In-Reply-To: <C13DBBE85AD6974B85C118C35890CA5F13B1B2F0AB@EU1RDCRDC1WX029.exi.nxp.com>

On Tuesday 14 October 2008 09:31:51 Daniel J Laird wrote:
> We have a 2D chip on board with our silicon.
> It too had a driver in kernel space and a queue in user space that allowed us to post jobs to it like Blit/Fill etc.
> 
> We run DirectFB on the platform and we too needed to get Physical addresses etc.
> 
> In the end what we did was use Linux FB device to allocate our memory.
> 
> We allocate more than we need in the FB device when it is created.
> The Double buffered FB comes first and what is left over we use as scratch memory.
> This allows us to create surfaces etc in this 'scratch' memory.
> When we need the physical address we use the offset from FB base in virtual memory (user space) and add that the physical base address of FB device.
> This then allows us to post the job to the 2D hardware.
> 
> This approach has worked on 2 different chips now.  Maybe this might help for you?

This is something I hadn't concidered - I'd forgotton that fbdev lets you query
the physical address of the framebuffer. I'm not sure it's possible to ask the
framebuffer device to use more memory however. The amount of memory is returned
in the fb_fix_screeninfo struct. I guess you must have implemented your own 
fbdev driver?

On a side note, I'm also tracking the DRM discussions on an in-kernel memory
manager for graphics. It seems a lot of what I'm trying to do is exactly what
the new memory manager is supposed to do. It sounds like this is also what you
needed? I'm asking because the DRM+GEM+modesetting API looks like a good API
for basic hardware as well as the more advanced desktop hardware it's intended
for. It would be really good if one day kernel graphics APIs could be unified.


Cheers,

Tom

^ permalink raw reply

* Re: Getting physical addresses of mmap'd pages from userspace
From: Bill Gatliff @ 2008-10-14 15:47 UTC (permalink / raw)
  To: Tom Cooksey; +Cc: linux-embedded mailing list
In-Reply-To: <200810140836.27710.thomas.cooksey@trolltech.com>

Tom Cooksey wrote:

> <RANT>
> What I don't understand is that I'm trying to do some pretty interesting & cool
> stuff with their processors (of course I would say that!), which will probably
> help them sell more units. Why then do they make it so difficult to work with
> them? It feels like they're shooting themselves in the foot. Madness.
> </RANT>

Not to perpetuate this further, but I can't resist...  :)

That's because their product won't stand on its own; it needs vendor lock-in to
be successful.  There really isn't any other explanation for such behavior.

Think like a biologist.  If an organism does something, then the upside must be
better then the downside of NOT doing that something, or the organism wouldn't
waste scarce time and energy doing it--- no matter how ridiculous that something
might be.  Unusual markings, mating calls, mullet haircuts...

One would think that in the world of high-technology, there would be a huge
upside to making products easy to use, which would naturally require free
availability of documentation and code (among other things).  But vendors seem
to work contrary to that objective, which must mean that there's an even bigger
upside to NOT making a product easy to use.

Put another way, their revenue stream depends on making your life as painful as
possible, so that you won't want to risk repeating that pain by switching to a
competitor's product.  It's a "shock collar ^K^K^K electrically-enhanced
training aid", so to speak, and we're the dogs.  And not the
chihuahua-in-Paris-Hilton's-purse kind of dogs, either.

Here's more evidence to support my point: what exactly is the cost to release
documentation without an NDA?  About US$0, which is considerably less than the
expense of executing an NDA.  So why have the NDA?  Because that expense must be
an "investment" in something that nets a larger return to the vendor of the
documents in question.  What might that be?  Hmmm....


Just my US$0.02.  You can keep the change.  :)


b.g.
-- 
Bill Gatliff
bgat@billgatliff.com

^ permalink raw reply

* Re: Getting physical addresses of mmap'd pages from userspace
From: Tom Cooksey @ 2008-10-15  7:06 UTC (permalink / raw)
  To: Bill Gatliff; +Cc: linux-embedded mailing list
In-Reply-To: <48F4BEFA.10801@billgatliff.com>

On Tuesday 14 October 2008 17:47:06 Bill Gatliff wrote:
> Tom Cooksey wrote:
> 
> > <RANT>
> > What I don't understand is that I'm trying to do some pretty interesting & cool
> > stuff with their processors (of course I would say that!), which will probably
> > help them sell more units. Why then do they make it so difficult to work with
> > them? It feels like they're shooting themselves in the foot. Madness.
> > </RANT>
> 
> Not to perpetuate this further, but I can't resist...  :)
> 
> That's because their product won't stand on its own; it needs vendor lock-in to
> be successful.  There really isn't any other explanation for such behavior.
> 
> Think like a biologist.  If an organism does something, then the upside must be
> better then the downside of NOT doing that something, or the organism wouldn't
> waste scarce time and energy doing it--- no matter how ridiculous that something
> might be.  Unusual markings, mating calls, mullet haircuts...
> 
> One would think that in the world of high-technology, there would be a huge
> upside to making products easy to use, which would naturally require free
> availability of documentation and code (among other things).  But vendors seem
> to work contrary to that objective, which must mean that there's an even bigger
> upside to NOT making a product easy to use.
> 
> Put another way, their revenue stream depends on making your life as painful as
> possible, so that you won't want to risk repeating that pain by switching to a
> competitor's product.  It's a "shock collar ^K^K^K electrically-enhanced
> training aid", so to speak, and we're the dogs.  And not the
> chihuahua-in-Paris-Hilton's-purse kind of dogs, either.
> 
> Here's more evidence to support my point: what exactly is the cost to release
> documentation without an NDA?  About US$0, which is considerably less than the
> expense of executing an NDA.  So why have the NDA?  Because that expense must be
> an "investment" in something that nets a larger return to the vendor of the
> documents in question.  What might that be?  Hmmm....

I always assumed it's because releasing the source opens them up to patent
infringement law suits? Some companies are more paranoid than others.

^ permalink raw reply

* Re: Getting physical addresses of mmap'd pages from userspace
From: James Chapman @ 2008-10-15  8:30 UTC (permalink / raw)
  To: Tom Cooksey; +Cc: Bill Gatliff, linux-embedded mailing list
In-Reply-To: <200810150906.41064.thomas.cooksey@trolltech.com>

Tom Cooksey wrote:
> On Tuesday 14 October 2008 17:47:06 Bill Gatliff wrote:
>> Tom Cooksey wrote:
>>
>>> <RANT>
>>> What I don't understand is that I'm trying to do some pretty interesting & cool
>>> stuff with their processors (of course I would say that!), which will probably
>>> help them sell more units. Why then do they make it so difficult to work with
>>> them? It feels like they're shooting themselves in the foot. Madness.
>>> </RANT>
>> Not to perpetuate this further, but I can't resist...  :)
>>
>> That's because their product won't stand on its own; it needs vendor lock-in to
>> be successful.  There really isn't any other explanation for such behavior.
>>
>> Think like a biologist.  If an organism does something, then the upside must be
>> better then the downside of NOT doing that something, or the organism wouldn't
>> waste scarce time and energy doing it--- no matter how ridiculous that something
>> might be.  Unusual markings, mating calls, mullet haircuts...
>>
>> One would think that in the world of high-technology, there would be a huge
>> upside to making products easy to use, which would naturally require free
>> availability of documentation and code (among other things).  But vendors seem
>> to work contrary to that objective, which must mean that there's an even bigger
>> upside to NOT making a product easy to use.
>>
>> Put another way, their revenue stream depends on making your life as painful as
>> possible, so that you won't want to risk repeating that pain by switching to a
>> competitor's product.  It's a "shock collar ^K^K^K electrically-enhanced
>> training aid", so to speak, and we're the dogs.  And not the
>> chihuahua-in-Paris-Hilton's-purse kind of dogs, either.
>>
>> Here's more evidence to support my point: what exactly is the cost to release
>> documentation without an NDA?  About US$0, which is considerably less than the
>> expense of executing an NDA.  So why have the NDA?  Because that expense must be
>> an "investment" in something that nets a larger return to the vendor of the
>> documents in question.  What might that be?  Hmmm....
> 
> I always assumed it's because releasing the source opens them up to patent
> infringement law suits? Some companies are more paranoid than others.

Some companies won't publish open software interface docs for their
devices because they think doing so will give their competitors an
advantage by giving away some info about how their device works
internally. They don't understand open source, period.

It's important to choose hardware devices carefully when developing an
Embedded Linux product; avoid any devices which don't have open source
drivers or docs whenever possible.

All device vendors understand money. If they start to lose market share
because Qt isn't supported by their devices then they might change their
approach. :)


-- 
James Chapman
Katalix Systems Ltd
http://www.katalix.com
Catalysts for your Embedded Linux software development

^ permalink raw reply

* [PATCH 0/6] Generic PWM API implementation
From: Bill Gatliff @ 2008-10-15 18:14 UTC (permalink / raw)
  To: linux-embedded; +Cc: Bill Gatliff

This series implements a Generic PWM Device API, including reference
implementations for the Atmel PWMC device, an LED device, and an LED
trigger.  It is based on linux-2.6.27.

This API is motivated by the author's need to support pluggable
devices; a secondary objective is to consolidate the existing PWM
implementations behind an agreeable, consistent, redundancy-reducing
interface.

The code included in this patch series draws heavily from the existing
PWM infrastructure and driver for the AT91SAM9263 PWMC.  The author is
grateful to Russell King, Eric Miao, David Brownell and many others
for providing such tall "shoulders" to stand upon.  Modifications to
their code as described in this changeset should not be interpreted as
attempts to address shortcomings, but rather to extend functionality
in ways that were not originally required.

The implementation of the Generic PWM Device API is structurally
similar to the generic GPIO API, except that the PWM code uses
platform bus_id strings instead of integers to identify target
deviices.  A configuration structure is also provided, both to
facilitate atomic hardware state changes and so that the API can be
extended in a source-code-compatible way to accomodate devices with
features not anticipated by the current code.

Pulse width modulated signals are used in an astounding number and
range of applications, and there is no "one true way" of either
realizing them or employing them to accomplish real work.  The review
process revealed many use cases to the author, but countless others
undoubtedly remain.  The current API attempts to provide a useful
feature set for the most basic users, packaged in such a way as to
allow the API to be extended in a backwards-compatible way as new
needs are identified.

The proposed code has been run-tested on a Cogent CSB737
(AT91SAM9263), mated to a custom circuit that drives multiple DC
motors and sensors.


Feedback is welcome!



b.g.
--
Bill Gatliff
<bgat@billgatliff.com>

---

Bill Gatliff (6):
  [PWM] Generic PWM API implementation
  [PWM] Changes to existing include/linux/pwm.h to adapt to generic PWM
    API
  [PWM] Documentation
  [PWM] Driver for Atmel PWMC peripheral
  [PWM] Install new Atmel PWMC driver in Kconfig, expunge old one
  [PWM] New LED driver and trigger that use PWM API

 Documentation/pwm.txt      |  258 +++++++++++++++++
 arch/arm/Kconfig           |    2 +
 drivers/Makefile           |    2 +
 drivers/leds/Kconfig       |   24 ++-
 drivers/leds/Makefile      |    2 +
 drivers/leds/leds-pwm.c    |  167 +++++++++++
 drivers/leds/ledtrig-dim.c |   95 ++++++
 drivers/misc/Kconfig       |    9 -
 drivers/misc/Makefile      |    1 -
 drivers/misc/atmel_pwm.c   |  409 --------------------------
 drivers/pwm/Kconfig        |   24 ++
 drivers/pwm/Makefile       |    6 +
 drivers/pwm/atmel-pwm.c    |  633 ++++++++++++++++++++++++++++++++++++++++
 drivers/pwm/pwm.c          |  681 ++++++++++++++++++++++++++++++++++++++++++++
 include/linux/pwm-led.h    |   34 +++
 include/linux/pwm.h        |  172 ++++++++++--
 16 files changed, 2074 insertions(+), 445 deletions(-)
 create mode 100644 Documentation/pwm.txt
 create mode 100644 drivers/leds/leds-pwm.c
 create mode 100644 drivers/leds/ledtrig-dim.c
 delete mode 100644 drivers/misc/atmel_pwm.c
 create mode 100644 drivers/pwm/Kconfig
 create mode 100644 drivers/pwm/Makefile
 create mode 100644 drivers/pwm/atmel-pwm.c
 create mode 100644 drivers/pwm/pwm.c
 create mode 100644 include/linux/pwm-led.h

^ permalink raw reply

* [PATCH 1/6] [PWM] Generic PWM API implementation
From: Bill Gatliff @ 2008-10-15 18:14 UTC (permalink / raw)
  To: linux-embedded; +Cc: Bill Gatliff
In-Reply-To: <cover.1224093510.git.bgat@billgatliff.com>

An API that consolidates PWM-capable devices behind a common interface.

This code defines a pwm_device structure, which identifies a PWM-capable
platform device having one or more output channels of type pwm_channel.  The
pwm_register() function maintains a list of pwm_devices; the pwm_request()
and pwm_free() functions allocate and un-allocate PWM channels on user
request.

The pwm_config_nosleep() and pwm_config() functions are the main entry
points into the PWM device driver itself.  The former may be called
from contexts that are not allowed to sleep, i.e. interrupt handlers, and
will return an error if the requested configuration change cannot be
performed by the hardware without sleeping; the latter may be called
everywhere else.

PWM drivers might sleep because some hardware makes it difficult to update
the period, duty cycle or polarity of an output while the peripheral is
running.  The Atmel PWMC peripheral is one example: the duty cycle and
period control registers share a single buffer register, but some
updates require changes to both registers and/or registers that aren't
buffered.  When that is required, you must stop the peripheral at
the end of a PWM period in order to make the state change.  The Atmel PWMC
driver implements the stop by sleeping until the end of the period.

An alternative implementation is to simply brute-force stop the PWM,
update the peripheral control registers, and then restart the hardware.
That approach is fine if you don't care about glitches in the PWM output.
The Atmel PWMC driver tries to be more elegant, at the expense of some
complexity and occasional conflict with sleep-prohibited contexts.

If you don't need to sleep or you don't care about glitch-free
operation, then put everything into pwm_config_nosleep()
and redirect pwm_config() there.

There are various helper functions that build the pwm_channel_config
structure for simple updates, like just modifying the duty cycle
percentage.  These should probably be inlined, but aren't.

PWM channels can be "synchronized".  As defined by this API, synchronized
PWM channels all start, stop and restart together when any channel in the
group is started, stopped, or restarted.  The Atmel PWMC driver emulates
this behavior; other hardware might support it more explicitly.  There is
currently no implementation to synchronize channels across PWM devices,
but the API will allow for that if someone wants to write the code.
Hint: there will be a measurable delay between starting and stopping the
various channels in a cross-device synchronization scenario, because
you'll have to start and stop each channel individually.

Finally, the pwm_set_handler() function registers a callback function that
the API will invoke in a worker thread at the end of the PWM period.

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

diff --git a/drivers/pwm/pwm.c b/drivers/pwm/pwm.c
new file mode 100644
index 0000000..5009de9
--- /dev/null
+++ b/drivers/pwm/pwm.c
@@ -0,0 +1,681 @@
+/*
+ * drivers/pwm/pwm.c
+ *
+ * Copyright (C) 2008 Bill Gatliff
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <linux/fs.h>
+#include <linux/completion.h>
+#include <linux/workqueue.h>
+#include <linux/pwm.h>
+
+
+static int __pwm_create_sysfs(struct pwm_device *pwm);
+
+static LIST_HEAD(pwm_device_list);
+static DEFINE_MUTEX(device_list_mutex);
+static struct class pwm_class;
+static struct workqueue_struct *pwm_handler_workqueue;
+
+
+int pwm_register(struct pwm_device *pwm)
+{
+	struct pwm_channel *p;
+	int wchan;
+	int ret = 0;
+
+	spin_lock_init(&pwm->list_lock);
+
+	p = kcalloc(pwm->nchan, sizeof(struct pwm_channel), GFP_KERNEL);
+	if (!p)
+		return -ENOMEM;
+
+	for (wchan = 0; wchan < pwm->nchan; wchan++) {
+		spin_lock_init(&p[wchan].lock);
+		init_completion(&p[wchan].complete);
+		p[wchan].chan = wchan;
+		p[wchan].pwm = pwm;
+	}
+
+	pwm->channels = p;
+
+	mutex_lock(&device_list_mutex);
+
+	list_add_tail(&pwm->list, &pwm_device_list);
+	ret = __pwm_create_sysfs(pwm);
+	if (ret) {
+		mutex_unlock(&device_list_mutex);
+		goto err_create_sysfs;
+	}
+
+	mutex_unlock(&device_list_mutex);
+
+	pr_info("%s: %d channels\n", pwm->bus_id, pwm->nchan);
+	return 0;
+
+err_create_sysfs:
+	kfree(p);
+
+	return ret;
+}
+EXPORT_SYMBOL(pwm_register);
+
+
+static int __match_device(struct device *dev, void *data)
+{
+	return dev_get_drvdata(dev) == data;
+}
+
+
+int pwm_unregister(struct pwm_device *pwm)
+{
+	int wchan;
+	struct device *dev;
+
+	mutex_lock(&device_list_mutex);
+
+	for (wchan = 0; wchan < pwm->nchan; wchan++) {
+		if (pwm->channels[wchan].flags & FLAG_REQUESTED) {
+			mutex_unlock(&device_list_mutex);
+			return -EBUSY;
+		}
+	}
+
+	for (wchan = 0; wchan < pwm->nchan; wchan++) {
+		dev = class_find_device(&pwm_class, NULL,
+					&pwm->channels[wchan],
+					__match_device);
+		if (dev) {
+			put_device(dev);
+			device_unregister(dev);
+		}
+	}
+
+	kfree(pwm->channels);
+	list_del(&pwm->list);
+	mutex_unlock(&device_list_mutex);
+
+	return 0;
+}
+EXPORT_SYMBOL(pwm_unregister);
+
+
+static struct pwm_device *
+__pwm_find_device(const char *bus_id)
+{
+	struct pwm_device *p;
+
+	list_for_each_entry(p, &pwm_device_list, list)
+	{
+		if (!strcmp(bus_id, p->bus_id))
+			return p;
+	}
+	return NULL;
+}
+
+
+static int
+__pwm_request_channel(struct pwm_channel *p,
+		      const char *requester)
+{
+	int ret;
+
+	if (test_and_set_bit(FLAG_REQUESTED, &p->flags))
+		return -EBUSY;
+
+	if (p->pwm->request) {
+		ret = p->pwm->request(p);
+		if (ret) {
+			clear_bit(FLAG_REQUESTED, &p->flags);
+			return ret;
+		}
+	}
+
+	p->requester = requester;
+	return 0;
+}
+
+
+struct pwm_channel *
+pwm_request(const char *bus_id,
+	    int chan,
+	    const char *requester)
+{
+	struct pwm_device *p;
+	int ret;
+
+	mutex_lock(&device_list_mutex);
+
+	p = __pwm_find_device(bus_id);
+	if (!p || chan >= p->nchan)
+		goto err_no_device;
+
+	if (!try_module_get(p->owner))
+		goto err_module_get_failed;
+
+	ret = __pwm_request_channel(&p->channels[chan], requester);
+	if (ret)
+		goto err_request_failed;
+
+	mutex_unlock(&device_list_mutex);
+
+	pr_debug("%s: %s:%d returns %p\n", __func__,
+		 bus_id, chan, &p->channels[chan]);
+
+	return &p->channels[chan];
+
+err_request_failed:
+	module_put(p->owner);
+err_module_get_failed:
+err_no_device:
+
+	mutex_unlock(&device_list_mutex);
+
+	pr_debug("%s: %s:%d returns NULL\n",
+		 __func__, bus_id, chan);
+
+	return NULL;
+}
+EXPORT_SYMBOL(pwm_request);
+
+
+void pwm_free(struct pwm_channel *p)
+{
+	mutex_lock(&device_list_mutex);
+
+	if (!test_and_clear_bit(FLAG_REQUESTED, &p->flags))
+		goto done;
+
+	pwm_stop(p);
+	pwm_unsynchronize(p, NULL);
+	pwm_set_handler(p, NULL, NULL);
+
+	if (p->pwm->free)
+		p->pwm->free(p);
+	module_put(p->pwm->owner);
+
+	pr_debug("%s: %s:%d free\n",
+		 __func__, p->pwm->bus_id, p->chan);
+
+done:
+	mutex_unlock(&device_list_mutex);
+}
+EXPORT_SYMBOL(pwm_free);
+
+
+unsigned long pwm_ns_to_ticks(struct pwm_channel *p,
+			      unsigned long nsecs)
+{
+	unsigned long long ticks;
+
+	ticks = nsecs;
+	ticks *= p->tick_hz;
+	do_div(ticks, 1000000000);
+	return ticks;
+}
+EXPORT_SYMBOL(pwm_ns_to_ticks);
+
+
+unsigned long pwm_ticks_to_ns(struct pwm_channel *p,
+			      unsigned long ticks)
+{
+	unsigned long long ns;
+
+	if (!p->tick_hz)
+		return 0;
+
+	ns = ticks;
+	ns *= 1000000000UL;
+	do_div(ns, p->tick_hz);
+	return ns;
+}
+EXPORT_SYMBOL(pwm_ticks_to_ns);
+
+
+static void
+pwm_config_ns_to_ticks(struct pwm_channel *p,
+		       struct pwm_channel_config *c)
+{
+	if (c->config_mask & PWM_CONFIG_PERIOD_NS) {
+		c->period_ticks = pwm_ns_to_ticks(p, c->period_ns);
+		c->config_mask &= ~PWM_CONFIG_PERIOD_NS;
+		c->config_mask |= PWM_CONFIG_PERIOD_TICKS;
+	}
+
+	if (c->config_mask & PWM_CONFIG_DUTY_NS) {
+		c->duty_ticks = pwm_ns_to_ticks(p, c->duty_ns);
+		c->config_mask &= ~PWM_CONFIG_DUTY_NS;
+		c->config_mask |= PWM_CONFIG_DUTY_TICKS;
+	}
+}
+
+
+static void
+pwm_config_percent_to_ticks(struct pwm_channel *p,
+			    struct pwm_channel_config *c)
+{
+	if (c->config_mask & PWM_CONFIG_DUTY_PERCENT) {
+		if (c->config_mask & PWM_CONFIG_PERIOD_TICKS)
+			c->duty_ticks = c->period_ticks;
+		else
+			c->duty_ticks = p->period_ticks;
+
+		c->duty_ticks *= c->duty_percent;
+		c->duty_ticks /= 100;
+		c->config_mask &= ~PWM_CONFIG_DUTY_PERCENT;
+		c->config_mask |= PWM_CONFIG_DUTY_TICKS;
+	}
+}
+
+
+int pwm_config_nosleep(struct pwm_channel *p,
+		       struct pwm_channel_config *c)
+{
+	if (!p->pwm->config_nosleep)
+		return -EINVAL;
+
+	pwm_config_ns_to_ticks(p, c);
+	pwm_config_percent_to_ticks(p, c);
+
+	return p->pwm->config_nosleep(p, c);
+}
+EXPORT_SYMBOL(pwm_config_nosleep);
+
+
+int pwm_config(struct pwm_channel *p,
+	       struct pwm_channel_config *c)
+{
+	int ret = 0;
+
+	if (unlikely(!p->pwm->config)) {
+		pr_debug("%s: %s:%d has no config handler (-EINVAL)\n",
+			 __func__, p->pwm->bus_id, p->chan);
+		return -EINVAL;
+	}
+
+	pwm_config_ns_to_ticks(p, c);
+	pwm_config_percent_to_ticks(p, c);
+
+	switch (c->config_mask & (PWM_CONFIG_PERIOD_TICKS
+				  | PWM_CONFIG_DUTY_TICKS)) {
+	case PWM_CONFIG_PERIOD_TICKS:
+		if (p->duty_ticks > c->period_ticks) {
+			ret = -EINVAL;
+			goto err;
+		}
+		break;
+	case PWM_CONFIG_DUTY_TICKS:
+		if (p->period_ticks < c->duty_ticks) {
+			ret = -EINVAL;
+			goto err;
+		}
+		break;
+	case PWM_CONFIG_DUTY_TICKS | PWM_CONFIG_PERIOD_TICKS:
+		if (c->duty_ticks > c->period_ticks) {
+			ret = -EINVAL;
+			goto err;
+		}
+		break;
+	default:
+		break;
+	}
+
+err:
+	pr_debug("%s: config_mask %d period_ticks %lu duty_ticks %lu"
+		 " polarity %d duty_ns %lu period_ns %lu duty_percent %d\n",
+		 __func__, c->config_mask, c->period_ticks, c->duty_ticks,
+		 c->polarity, c->duty_ns, c->period_ns, c->duty_percent);
+
+	if (ret)
+		return ret;
+	return p->pwm->config(p, c);
+}
+EXPORT_SYMBOL(pwm_config);
+
+
+int pwm_set_period_ns(struct pwm_channel *p,
+		      unsigned long period_ns)
+{
+	struct pwm_channel_config c = {
+		.config_mask = PWM_CONFIG_PERIOD_TICKS,
+		.period_ticks = pwm_ns_to_ticks(p, period_ns),
+	};
+
+	return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_set_period_ns);
+
+
+unsigned long pwm_get_period_ns(struct pwm_channel *p)
+{
+	return pwm_ticks_to_ns(p, p->period_ticks);
+}
+EXPORT_SYMBOL(pwm_get_period_ns);
+
+
+int pwm_set_duty_ns(struct pwm_channel *p,
+		    unsigned long duty_ns)
+{
+	struct pwm_channel_config c = {
+		.config_mask = PWM_CONFIG_DUTY_TICKS,
+		.duty_ticks = pwm_ns_to_ticks(p, duty_ns),
+	};
+	return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_set_duty_ns);
+
+
+unsigned long pwm_get_duty_ns(struct pwm_channel *p)
+{
+	return pwm_ticks_to_ns(p, p->duty_ticks);
+}
+EXPORT_SYMBOL(pwm_get_duty_ns);
+
+
+int pwm_set_duty_percent(struct pwm_channel *p,
+			 int percent)
+{
+	struct pwm_channel_config c = {
+		.config_mask = PWM_CONFIG_DUTY_PERCENT,
+		.duty_percent = percent,
+	};
+	return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_set_duty_percent);
+
+
+int pwm_set_polarity(struct pwm_channel *p,
+		     int active_high)
+{
+	struct pwm_channel_config c = {
+		.config_mask = PWM_CONFIG_POLARITY,
+		.polarity = active_high,
+	};
+	return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_set_polarity);
+
+
+int pwm_start(struct pwm_channel *p)
+{
+	struct pwm_channel_config c = {
+		.config_mask = PWM_CONFIG_START,
+	};
+	return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_start);
+
+
+int pwm_stop(struct pwm_channel *p)
+{
+	struct pwm_channel_config c = {
+		.config_mask = PWM_CONFIG_STOP,
+	};
+	return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_stop);
+
+
+int pwm_synchronize(struct pwm_channel *p,
+		    struct pwm_channel *to_p)
+{
+	if (p->pwm != to_p->pwm) {
+		/* TODO: support cross-device synchronization */
+		return -EINVAL;
+	}
+
+	if (!p->pwm->synchronize)
+		return -EINVAL;
+
+	return p->pwm->synchronize(p, to_p);
+}
+EXPORT_SYMBOL(pwm_synchronize);
+
+
+int pwm_unsynchronize(struct pwm_channel *p,
+		      struct pwm_channel *from_p)
+{
+	if (from_p && (p->pwm != from_p->pwm)) {
+		/* TODO: support cross-device synchronization */
+		return -EINVAL;
+	}
+
+	if (!p->pwm->unsynchronize)
+		return -EINVAL;
+
+	return p->pwm->unsynchronize(p, from_p);
+}
+EXPORT_SYMBOL(pwm_unsynchronize);
+
+
+static void pwm_handler(struct work_struct *w)
+{
+	struct pwm_channel *p = container_of(w, struct pwm_channel,
+					     handler_work);
+	if (p->handler && p->handler(p, p->handler_data))
+		pwm_stop(p);
+}
+
+
+static void __pwm_callback(struct pwm_channel *p)
+{
+	queue_work(pwm_handler_workqueue, &p->handler_work);
+	pr_debug("%s:%d handler %p scheduled with data %p\n",
+		 p->pwm->bus_id, p->chan, p->handler, p->handler_data);
+}
+
+
+int pwm_set_handler(struct pwm_channel *p,
+		    pwm_handler_t handler,
+		    void *data)
+{
+	if (p->pwm->set_callback) {
+		p->handler_data = data;
+		p->handler = handler;
+		INIT_WORK(&p->handler_work, pwm_handler);
+		return p->pwm->set_callback(p, handler ? __pwm_callback : NULL);
+	}
+	return -EINVAL;
+}
+EXPORT_SYMBOL(pwm_set_handler);
+
+
+static ssize_t pwm_run_store(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf,
+			     size_t len)
+{
+	struct pwm_channel *p = dev_get_drvdata(dev);
+	if (sysfs_streq(buf, "1"))
+		pwm_start(p);
+	else if (sysfs_streq(buf, "0"))
+		pwm_stop(p);
+	return len;
+}
+static DEVICE_ATTR(run, 0200, NULL, pwm_run_store);
+
+
+static ssize_t pwm_duty_ns_show(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	struct pwm_channel *p = dev_get_drvdata(dev);
+	return sprintf(buf, "%lu\n", pwm_get_duty_ns(p));
+}
+
+static ssize_t pwm_duty_ns_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf,
+				 size_t len)
+{
+	unsigned long duty_ns;
+	struct pwm_channel *p = dev_get_drvdata(dev);
+
+	if (1 == sscanf(buf, "%lu", &duty_ns))
+		pwm_set_duty_ns(p, duty_ns);
+	return len;
+}
+static DEVICE_ATTR(duty_ns, 0644, pwm_duty_ns_show, pwm_duty_ns_store);
+
+
+static ssize_t pwm_period_ns_show(struct device *dev,
+				  struct device_attribute *attr,
+				  char *buf)
+{
+	struct pwm_channel *p = dev_get_drvdata(dev);
+	return sprintf(buf, "%lu\n", pwm_get_period_ns(p));
+}
+
+
+static ssize_t pwm_period_ns_store(struct device *dev,
+				   struct device_attribute *attr,
+				   const char *buf,
+				   size_t len)
+{
+	unsigned long period_ns;
+	struct pwm_channel *p = dev_get_drvdata(dev);
+
+	if (1 == sscanf(buf, "%lu", &period_ns))
+		pwm_set_period_ns(p, period_ns);
+	return len;
+}
+static DEVICE_ATTR(period_ns, 0644, pwm_period_ns_show, pwm_period_ns_store);
+
+
+static ssize_t pwm_polarity_show(struct device *dev,
+				 struct device_attribute *attr,
+				 char *buf)
+{
+	struct pwm_channel *p = dev_get_drvdata(dev);
+	return sprintf(buf, "%d\n", p->active_low ? 0 : 1);
+}
+
+
+static ssize_t pwm_polarity_store(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf,
+				  size_t len)
+{
+	int polarity;
+	struct pwm_channel *p = dev_get_drvdata(dev);
+
+	if (1 == sscanf(buf, "%d", &polarity))
+		pwm_set_polarity(p, polarity);
+	return len;
+}
+static DEVICE_ATTR(polarity, 0644, pwm_polarity_show, pwm_polarity_store);
+
+
+static ssize_t pwm_request_show(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	struct pwm_channel *p = dev_get_drvdata(dev);
+	mutex_lock(&device_list_mutex);
+	__pwm_request_channel(p, "sysfs");
+	mutex_unlock(&device_list_mutex);
+
+	return sprintf(buf, "%s\n", p->requester);
+}
+
+
+static ssize_t pwm_request_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf,
+				 size_t len)
+{
+	struct pwm_channel *p = dev_get_drvdata(dev);
+	pwm_free(p);
+	return len;
+}
+static DEVICE_ATTR(request, 0644, pwm_request_show, pwm_request_store);
+
+
+static const struct attribute *pwm_attrs[] =
+{
+	&dev_attr_run.attr,
+	&dev_attr_polarity.attr,
+	&dev_attr_duty_ns.attr,
+	&dev_attr_period_ns.attr,
+	&dev_attr_request.attr,
+	NULL,
+};
+
+
+static const struct attribute_group pwm_device_attr_group = {
+	.attrs = (struct attribute **)pwm_attrs,
+};
+
+
+static int __pwm_create_sysfs(struct pwm_device *pwm)
+{
+	int ret = 0;
+	struct device *dev;
+	int wchan;
+
+	for (wchan = 0; wchan < pwm->nchan; wchan++) {
+		dev = device_create(&pwm_class, pwm->dev, MKDEV(0, 0),
+				    pwm->channels + wchan,
+				    "%s:%d", pwm->bus_id, wchan);
+		if (!dev)
+			goto err_dev_create;
+		ret = sysfs_create_group(&dev->kobj, &pwm_device_attr_group);
+		if (ret)
+			goto err_dev_group_create;
+	}
+
+	return ret;
+
+err_dev_group_create:
+err_dev_create:
+	/* TODO: undo all the successful device_create calls */
+	return -ENODEV;
+}
+
+
+static struct class_attribute pwm_class_attrs[] = {
+	__ATTR_NULL,
+};
+
+static struct class pwm_class = {
+	.name = "pwm",
+	.owner = THIS_MODULE,
+
+	.class_attrs = pwm_class_attrs,
+};
+
+
+static int __init pwm_init(void)
+{
+	int ret;
+
+	/* TODO: how to deal with devices that register very early? */
+
+	ret = class_register(&pwm_class);
+	if (ret < 0)
+		return ret;
+
+	pwm_handler_workqueue = create_workqueue("pwmd");
+
+	return 0;
+}
+postcore_initcall(pwm_init);
-- 
1.5.6.5

^ permalink raw reply related

* [PATCH 2/6] [PWM] Changes to existing include/linux/pwm.h to adapt to generic PWM API
From: Bill Gatliff @ 2008-10-15 18:14 UTC (permalink / raw)
  To: linux-embedded; +Cc: Bill Gatliff
In-Reply-To: <cover.1224093510.git.bgat@billgatliff.com>

Modifications to the existing include/linux/pwm.h file for the generic PWM
API.

Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
 include/linux/pwm.h |  172 ++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 151 insertions(+), 21 deletions(-)

diff --git a/include/linux/pwm.h b/include/linux/pwm.h
index 3945f80..1a4308e 100644
--- a/include/linux/pwm.h
+++ b/include/linux/pwm.h
@@ -1,31 +1,161 @@
 #ifndef __LINUX_PWM_H
 #define __LINUX_PWM_H
 
-struct pwm_device;
-
 /*
- * pwm_request - request a PWM device
+ * include/linux/pwm.h
+ *
+ * Copyright (C) 2008 Bill Gatliff
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
-struct pwm_device *pwm_request(int pwm_id, const char *label);
 
-/*
- * pwm_free - free a PWM device
- */
-void pwm_free(struct pwm_device *pwm);
+enum {
+	PWM_CONFIG_DUTY_TICKS = BIT(0),
+	PWM_CONFIG_PERIOD_TICKS = BIT(1),
+	PWM_CONFIG_POLARITY = BIT(2),
+	PWM_CONFIG_START = BIT(3),
+	PWM_CONFIG_STOP = BIT(4),
 
-/*
- * pwm_config - change a PWM device configuration
- */
-int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
+	PWM_CONFIG_HANDLER = BIT(5),
 
-/*
- * pwm_enable - start a PWM output toggling
- */
-int pwm_enable(struct pwm_device *pwm);
+	PWM_CONFIG_DUTY_NS = BIT(6),
+	PWM_CONFIG_DUTY_PERCENT = BIT(7),
+	PWM_CONFIG_PERIOD_NS = BIT(8),
+};
+
+struct pwm_channel;
+struct work_struct;
+
+typedef int (*pwm_handler_t)(struct pwm_channel *p, void *data);
+typedef void (*pwm_callback_t)(struct pwm_channel *p);
+
+struct pwm_channel_config {
+	int config_mask;
+	unsigned long duty_ticks;
+	unsigned long period_ticks;
+	int polarity;
+
+	pwm_handler_t handler;
+
+	unsigned long duty_ns;
+	unsigned long period_ns;
+	int duty_percent;
+};
+
+struct pwm_device {
+	struct list_head list;
+	spinlock_t list_lock;
+	struct device *dev;
+	struct module *owner;
+	struct pwm_channel *channels;
+
+	const char *bus_id;
+	int nchan;
+
+	int	(*request)	(struct pwm_channel *p);
+	void	(*free)		(struct pwm_channel *p);
+	int	(*config)	(struct pwm_channel *p,
+				 struct pwm_channel_config *c);
+	int	(*config_nosleep)(struct pwm_channel *p,
+				  struct pwm_channel_config *c);
+	int	(*synchronize)	(struct pwm_channel *p,
+				 struct pwm_channel *to_p);
+	int	(*unsynchronize)(struct pwm_channel *p,
+				 struct pwm_channel *from_p);
+	int	(*set_callback)	(struct pwm_channel *p,
+				 pwm_callback_t callback);
+};
+
+int pwm_register(struct pwm_device *pwm);
+int pwm_unregister(struct pwm_device *pwm);
+
+enum {
+	FLAG_REQUESTED = 0,
+	FLAG_STOP = 1,
+};
+
+struct pwm_channel {
+	struct list_head list;
+	struct pwm_device *pwm;
+	const char *requester;
+	int chan;
+	unsigned long flags;
+	unsigned long tick_hz;
+
+	spinlock_t lock;
+	struct completion complete;
+
+	pwm_callback_t callback;
+
+	struct work_struct handler_work;
+	pwm_handler_t handler;
+	void *handler_data;
+
+	int active_low;
+	unsigned long period_ticks;
+	unsigned long duty_ticks;
+};
+
+struct pwm_channel *
+pwm_request(const char *bus_id, int chan,
+	    const char *requester);
+
+void pwm_free(struct pwm_channel *pwm);
+
+int pwm_config_nosleep(struct pwm_channel *pwm,
+		       struct pwm_channel_config *c);
+
+int pwm_config(struct pwm_channel *pwm,
+	       struct pwm_channel_config *c);
+
+unsigned long pwm_ns_to_ticks(struct pwm_channel *pwm,
+			      unsigned long nsecs);
+
+unsigned long pwm_ticks_to_ns(struct pwm_channel *pwm,
+			      unsigned long ticks);
+
+int pwm_set_period_ns(struct pwm_channel *pwm,
+		      unsigned long period_ns);
+
+unsigned long int pwm_get_period_ns(struct pwm_channel *pwm);
+
+int pwm_set_duty_ns(struct pwm_channel *pwm,
+		    unsigned long duty_ns);
+
+int pwm_set_duty_percent(struct pwm_channel *pwm,
+			 int percent);
+
+unsigned long pwm_get_duty_ns(struct pwm_channel *pwm);
+
+int pwm_set_polarity(struct pwm_channel *pwm,
+		     int active_high);
+
+int pwm_start(struct pwm_channel *pwm);
+
+int pwm_stop(struct pwm_channel *pwm);
+
+int pwm_set_handler(struct pwm_channel *pwm,
+		    pwm_handler_t handler,
+		    void *data);
+
+int pwm_synchronize(struct pwm_channel *p,
+		    struct pwm_channel *to_p);
+
+
+int pwm_unsynchronize(struct pwm_channel *p,
+		      struct pwm_channel *from_p);
 
-/*
- * pwm_disable - stop a PWM output toggling
- */
-void pwm_disable(struct pwm_device *pwm);
 
-#endif /* __ASM_ARCH_PWM_H */
+#endif /* __LINUX_PWM_H */
-- 
1.5.6.5

^ permalink raw reply related

* [PATCH 3/6] [PWM] Documentation
From: Bill Gatliff @ 2008-10-15 18:14 UTC (permalink / raw)
  To: linux-embedded; +Cc: Bill Gatliff
In-Reply-To: <cover.1224093510.git.bgat@billgatliff.com>


Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
 Documentation/pwm.txt |  258 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 258 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/pwm.txt

diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt
new file mode 100644
index 0000000..b8932dd
--- /dev/null
+++ b/Documentation/pwm.txt
@@ -0,0 +1,258 @@
+                       Generic PWM Device API
+
+                          October 8, 2008
+                           Bill Gatliff
+                       <bgat@billgatliff.com>
+
+
+
+The code in drivers/pwm and include/linux/pwm.h implements an API for
+applications involving pulse-width-modulation signals.  This document
+describes how the API implementation facilitates both PWM-generating
+devices, and users of those devices.
+
+
+
+Motivation
+
+The primary goals for implementing the "generic PWM API" are to
+consolidate the various PWM implementations within a consistent and
+redundancy-reducing framework, and to facilitate the use of
+hotpluggable PWM devices.
+
+Previous PWM-related implementations within the Linux kernel achieved
+their consistency via cut-and-paste, but did not need to (and didn't)
+facilitate more than one PWM-generating device within the system---
+hotplug or otherwise.  The Generic PWM Device API might be most
+appropriately viewed as an update to those implementations, rather
+than a complete rewrite.
+
+
+
+Challenges
+
+One of the difficulties in implementing a generic PWM framework is the
+fact that pulse-width-modulation applications involve real-world
+signals, which often must be carefully managed to prevent destruction
+of hardware that is linked to those signals.  A DC motor that
+experiences a brief interruption in the PWM signal controlling it
+might destructively overheat; it could change speed, losing
+synchronization with a sensor; it could even suddenly change direction
+or torque, breaking the mechanical device connected to it.
+
+(A generic PWM device framework is not directly responsible for
+preventing the above scenarios: that responsibility lies with the
+hardware designer and the application and driver authors.  But it must
+to the greatest extent possible make it easy to avoid such problems).
+
+A generic PWM device framework must accomodate the substantial
+differences between available PWM-generating hardware devices, without
+becoming sub-optimal for any of them.
+
+Finally, a generic PWM device framework must be relatively
+lightweight, computationally speaking.  Some PWM users demand
+high-speed outputs, plus the ability to regulate those outputs
+quickly.  A device framework must be able to "keep up" with such
+hardware, while still leaving time to do real work.
+
+The Generic PWM Device API is an attempt to meet all of the above
+requirements.  At its initial publication, the API was already in use
+managing small DC motors through a custom-designed, optically-isolated
+H-bridge driver.
+
+
+
+Functional Overview
+
+The Generic PWM Device API framework is implemented in
+include/linux/pwm.h and drivers/pwm/pwm.c.  The functions therein use
+information from pwm_device, pwm_channel and pwm_channel_config
+structures to invoke services in PWM peripheral device drivers.
+Consult drivers/pwm/atmel-pwm.c for an example driver.
+
+There are two classes of adopters of the PWM framework:
+
+  "Users" -- those wishing to employ the API merely to produce PWM
+  signals; once they have identified the appropriate physical output
+  on the platform in question, they don't care about the details of
+  the underlying hardware
+
+  "Driver authors" -- those wishing to bind devices that can generate
+  PWM signals to the Generic PWM Device API, so that the services of
+  those devices become available to users; assuming the hardware can
+  support the needs of a user, driver authors don't care about the
+  details of the user's application
+
+Generally speaking, users will first invoke pwm_request() to obtain a
+handle to a PWM device.  They will then pass that handle to functions
+like pwm_duty_ns() and pwm_period_ns() to set the duty cycle and
+period of the PWM signal, respectively.  They will also invoke
+pwm_start() and pwm_stop() to turn the signal on and off.
+
+The framework also provides a sysfs interface to PWM devices, which is
+adequate for basic needs and testing.
+
+Driver authors fill out a pwm_device structure, which describes the
+capabilities of the PWM hardware being driven--- including the number
+of distinct output "channels" the peripheral offers.  They then invoke
+pwm_register() (usually from within their device's probe() handler) to
+make the PWM API aware of their device.  The framework will call back
+to the methods described in the pwm_device structure to configure and
+use the hardware.
+
+Note that PWM signals can be produced by a variety of peripherals,
+beyond the true "PWM hardware" offered by many system-on-chip devices.
+Other possibilities include timer/counters with compare-match
+capabilities, carefully-programmed synchronous serial ports
+(e.g. SPI), and GPIO pins driven by kernel interval timers.  With a
+proper pwm_device structure, these devices and pseudo-devices can all
+be accomodated by the Generic PWM Device API framework.
+
+
+
+Using the API to Generate PWM Signals -- Basic Functions for Users
+
+
+pwm_request() -- Returns a pwm_channel pointer, which is subsequently
+passed to the other user-related PWM functions.  Once requested, a PWM
+channel is marked as in-use and subsequent requests prior to
+pwm_free() will fail.
+
+The names used to refer to PWM devices are defined by driver authors.
+Typically they are platform device bus identifiers, and this
+convention is encouraged for consistency.
+
+
+pwm_free() -- Marks a PWM channel as no longer in use.  The PWM device
+is stopped before it is released by the API.
+
+
+pwm_period_ns() -- Specifies the PWM signal's period, in nanoseconds.
+
+
+pwm_duty_ns() -- Specifies the PWM signal's active duration, in nanoseconds.
+
+
+pwm_duty_percent() -- Specifies the PWM signal's active duration, as a
+percentage of the current period of the signal.  NOTE: this value is
+not recalculated if the period of the signal is subsequently changed.
+
+
+pwm_start(), pwm_stop() -- Turns the PWM signal on and off.  Except
+where stated otherwise by a driver author, signals are stopped at the
+end of the current period, at which time the output is set to its
+inactive state.
+
+
+pwm_polarity() -- Defines whether the PWM signal output's active
+region is "1" or "0".  A 10% duty-cycle, polarity=1 signal will
+conventionally be at 5V (or 3.3V, or 1000V, or whatever the platform
+hardware does) for 10% of the period.  The same configuration of a
+polarity=0 signal will be at 5V (or 3.3V, or ...) for 90% of the
+period.
+
+
+
+Using the API to Generate PWM Signals -- Advanced Functions
+
+
+pwm_config() -- Passes a pwm_channel_config structure to the
+associated device driver.  This function is invoked by pwm_start(),
+pwm_duty_ns(), etc. and is one of two main entry points to the PWM
+driver for the hardware being used.  The configuration change is
+guaranteed atomic if multiple configuration changes are specified.
+This function might sleep, depending on what the device driver has to
+do to satisfy the request.  All PWM device drivers must support this
+entry point.
+
+
+pwm_config_nosleep() -- Passes a pwm_channel_config structure to the
+associated device driver.  If the driver must sleep in order to
+implement the requested configuration change, -EWOULDBLOCK is
+returned.  Users may call this function from interrupt handlers, for
+example.  This is the other main entry point into the PWM hardware
+driver, but not all device drivers support this entry point.
+
+
+pwm_synchronize(), pwm_unsynchronize() -- "Synchronizes" two or more
+PWM channels, if the underlying hardware permits.  (If it doesn't, the
+framework facilitates emulating this capability but it is not yet
+implemented).  Synchronized channels will start and stop
+simultaneously when any single channel in the group is started or
+stopped.  Use pwm_unsynchronize(..., NULL) to completely detach a
+channel from any other synchronized channels.
+
+
+pwm_set_handler() -- Defines an end-of-period callback.  The indicated
+function will be invoked in a worker thread at the end of each PWM
+period, and can subsequently invoke pwm_config(), etc.  Must be used
+with extreme care for high-speed PWM outputs.  Set the handler
+function to NULL to un-set the handler.
+
+
+
+Implementing a PWM Device API Driver -- Functions for Driver Authors
+
+
+Fill out the appropriate fields in a pwm_device structure, and submit
+to pwm_register():
+
+
+bus_id -- the plaintext name of the device.  Users will bind to a
+channel on the device using this name plus the channel number.  For
+example, the Atmel PWMC's bus_id is "atmel_pwmc", the same as used by
+the platform device driver (recommended).  The first device registered
+thereby receives bus_id "atmel_pwmc.0", which is what you put in
+pwm_device.bus_id.  Channels are then named "atmel_pwmc.0:[0-3]".
+(Hint: just use pdev->dev.bus_id in your probe() method).
+
+
+nchan -- the number of distinct output channels provided by the device.
+
+
+request -- (optional) Invoked each time a user requests a channel.
+Use to turn on clocks, clean up register states, etc.  The framework
+takes care of device locking/unlocking; you will see only successful
+requests.
+
+
+free -- (optional) Callback for each time a user relinquishes a
+channel.  The framework will have already stopped, unsynchronized and
+un-handled the channel.  Use to turn off clocks, etc. as necessary.
+
+
+synchronize, unsynchronize -- (optional) Callbacks to
+synchronize/unsynchronize channels.  Some devices provide this
+capability in hardware; for others, it can be emulated (see
+atmel_pwmc.c's sync_mask for an example).
+
+
+set_callback -- (optional) Invoked when a user requests a handler.  If
+the hardware supports an end-of-period interrupt, invoke the function
+indicated during your interrupt handler.  The callback function itself
+is always internal to the API, and does not map directly to the user's
+callback function.
+
+
+config -- Invoked to change the device configuration, always from a
+sleep-capable context.  All the changes indicated must be performed
+atomically, ideally synchronized to an end-of-period event (so that
+you avoid short or long output pulses).  You may sleep, etc. as
+necessary within this function.
+
+
+config_nosleep -- (optional) Invoked to change device configuration
+from within a context that is not allowed to sleep.  If you cannot
+perform the requested configuration changes without sleeping, return
+-EWOULDBLOCK.
+
+
+
+Acknowledgements
+
+
+The author expresses his gratitude to the countless developers who
+have reviewed and submitted feedback on the various versions of the
+Generic PWM Device API code, and those who have submitted drivers and
+applications that use the framework.  You know who you are.  ;)
+
-- 
1.5.6.5

^ permalink raw reply related

* [PATCH 4/6] [PWM] Driver for Atmel PWMC peripheral
From: Bill Gatliff @ 2008-10-15 18:14 UTC (permalink / raw)
  To: linux-embedded; +Cc: Bill Gatliff
In-Reply-To: <cover.1224093510.git.bgat@billgatliff.com>

This driver binds the Atmel PWMC platform device to the Generic
PWM Device API.

The config_nosleep() function changes only the peripheral states that do
not require a sleep in order to avoid a spurious output; other state
changes are in config(), which calls stop_sync() to wait for the end of
the current PWM period.  Note that the PWMC hardware has limited
double-buffering: within some constraints, you can change either the
period OR the duty cycle with the hardware active, and the hardware will
commit the register update at the end of the current PWM period.

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

diff --git a/drivers/pwm/atmel-pwm.c b/drivers/pwm/atmel-pwm.c
new file mode 100644
index 0000000..c7ec676
--- /dev/null
+++ b/drivers/pwm/atmel-pwm.c
@@ -0,0 +1,633 @@
+/*
+ * drivers/pwm/atmel-pwm.c
+ *
+ * Copyright (C) 2008 Bill Gatliff
+ * Copyright (C) 2007 David Brownell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+
+enum {
+	/* registers common to the PWMC peripheral */
+	PWMC_MR = 0,
+	PWMC_ENA = 4,
+	PWMC_DIS = 8,
+	PWMC_SR = 0xc,
+	PWMC_IER = 0x10,
+	PWMC_IDR = 0x14,
+	PWMC_IMR = 0x18,
+	PWMC_ISR = 0x1c,
+
+	/* registers per each PWMC channel */
+	PWMC_CMR = 0,
+	PWMC_CDTY = 4,
+	PWMC_CPRD = 8,
+	PWMC_CCNT = 0xc,
+	PWMC_CUPD = 0x10,
+
+	/* how to find each channel */
+	PWMC_CHAN_BASE = 0x200,
+	PWMC_CHAN_STRIDE = 0x20,
+
+	/* CMR bits of interest */
+	PWMC_CMR_CPD = 10,
+	PWMC_CMR_CPOL = 9,
+	PWMC_CMR_CALG = 8,
+	PWMC_CMR_CPRE_MASK = 0xf,
+};
+
+struct atmel_pwm {
+	struct pwm_device pwm;
+	spinlock_t lock;
+	void __iomem *iobase;
+	struct clk *clk;
+	u32 *sync_mask;
+	int irq;
+	u32 ccnt_mask;
+};
+
+
+static inline void
+pwmc_writel(const struct atmel_pwm *p,
+	    unsigned offset, u32 val)
+{
+	__raw_writel(val, p->iobase + offset);
+}
+
+
+static inline u32
+pwmc_readl(const struct atmel_pwm *p,
+	   unsigned offset)
+{
+	return __raw_readl(p->iobase + offset);
+}
+
+
+static inline void
+pwmc_chan_writel(const struct pwm_channel *p,
+		 u32 offset, u32 val)
+{
+	const struct atmel_pwm *ap
+		= container_of(p->pwm, struct atmel_pwm, pwm);
+
+	if (PWMC_CMR == offset)
+		val &= ((1 << PWMC_CMR_CPD)
+			| (1 << PWMC_CMR_CPOL)
+			| (1 << PWMC_CMR_CALG)
+			| (PWMC_CMR_CPRE_MASK));
+	else
+		val &= ap->ccnt_mask;
+
+	pwmc_writel(ap, offset + PWMC_CHAN_BASE
+		    + (p->chan * PWMC_CHAN_STRIDE), val);
+}
+
+
+static inline u32
+pwmc_chan_readl(const struct pwm_channel *p,
+		u32 offset)
+{
+	const struct atmel_pwm *ap
+		= container_of(p->pwm, struct atmel_pwm, pwm);
+
+	return pwmc_readl(ap, offset + PWMC_CHAN_BASE
+			  + (p->chan * PWMC_CHAN_STRIDE));
+}
+
+
+static inline int
+__atmel_pwm_is_on(struct pwm_channel *p)
+{
+	struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+	return (pwmc_readl(ap, PWMC_SR) & (1 << p->chan)) ? 1 : 0;
+}
+
+
+static inline void
+__atmel_pwm_unsynchronize(struct pwm_channel *p,
+			  struct pwm_channel *to_p)
+{
+	const struct atmel_pwm *ap
+		= container_of(p->pwm, struct atmel_pwm, pwm);
+	int wchan;
+
+	if (to_p) {
+		ap->sync_mask[p->chan] &= ~(1 << to_p->chan);
+		ap->sync_mask[to_p->chan] &= ~(1 << p->chan);
+		goto done;
+	}
+
+	ap->sync_mask[p->chan] = 0;
+	for (wchan = 0; wchan < ap->pwm.nchan; wchan++)
+		ap->sync_mask[wchan] &= ~(1 << p->chan);
+done:
+	pr_debug("%s:%d sync_mask %x\n",
+		 p->pwm->bus_id, p->chan, ap->sync_mask[p->chan]);
+}
+
+
+static inline void
+__atmel_pwm_synchronize(struct pwm_channel *p,
+			struct pwm_channel *to_p)
+{
+	const struct atmel_pwm *ap
+		= container_of(p->pwm, struct atmel_pwm, pwm);
+
+	if (!to_p)
+		return;
+
+	ap->sync_mask[p->chan] |= (1 << to_p->chan);
+	ap->sync_mask[to_p->chan] |= (1 << p->chan);
+
+	pr_debug("%s:%d sync_mask %x\n",
+		 p->pwm->bus_id, p->chan, ap->sync_mask[p->chan]);
+}
+
+
+static inline void
+__atmel_pwm_stop(struct pwm_channel *p)
+{
+	struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+	u32 chid = 1 << p->chan;
+
+	pwmc_writel(ap, PWMC_DIS, ap->sync_mask[p->chan] | chid);
+}
+
+
+static inline void
+__atmel_pwm_start(struct pwm_channel *p)
+{
+	struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+	u32 chid = 1 << p->chan;
+
+	pwmc_writel(ap, PWMC_ENA, ap->sync_mask[p->chan] | chid);
+}
+
+
+static int
+atmel_pwm_synchronize(struct pwm_channel *p,
+		      struct pwm_channel *to_p)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&p->lock, flags);
+	__atmel_pwm_synchronize(p, to_p);
+	spin_unlock_irqrestore(&p->lock, flags);
+	return 0;
+}
+
+
+static int
+atmel_pwm_unsynchronize(struct pwm_channel *p,
+			struct pwm_channel *from_p)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&p->lock, flags);
+	__atmel_pwm_unsynchronize(p, from_p);
+	spin_unlock_irqrestore(&p->lock, flags);
+	return 0;
+}
+
+
+static inline int
+__atmel_pwm_config_polarity(struct pwm_channel *p,
+			    struct pwm_channel_config *c)
+{
+	u32 cmr = pwmc_chan_readl(p, PWMC_CMR);
+
+	if (c->polarity)
+		cmr &= ~BIT(PWMC_CMR_CPOL);
+	else
+		cmr |= BIT(PWMC_CMR_CPOL);
+	pwmc_chan_writel(p, PWMC_CMR, cmr);
+
+	pr_debug("%s:%d polarity %d\n", p->pwm->bus_id,
+		 p->chan, c->polarity);
+	return 0;
+}
+
+
+static inline int
+__atmel_pwm_config_duty_ticks(struct pwm_channel *p,
+			      struct pwm_channel_config *c)
+{
+	u32 cmr, cprd, cpre, cdty;
+
+	cmr = pwmc_chan_readl(p, PWMC_CMR);
+	cprd = pwmc_chan_readl(p, PWMC_CPRD);
+
+	cpre = cmr & PWMC_CMR_CPRE_MASK;
+	cmr &= ~BIT(PWMC_CMR_CPD);
+
+	cdty = cprd - (c->duty_ticks >> cpre);
+
+	p->duty_ticks = c->duty_ticks;
+
+	if (__atmel_pwm_is_on(p)) {
+		pwmc_chan_writel(p, PWMC_CMR, cmr);
+		pwmc_chan_writel(p, PWMC_CUPD, cdty);
+	} else
+		pwmc_chan_writel(p, PWMC_CDTY, cdty);
+
+	pr_debug("%s:%d duty_ticks = %lu cprd = %x"
+		 " cdty = %x cpre = %x\n",
+		 p->pwm->bus_id, p->chan, p->duty_ticks,
+		 cprd, cdty, cpre);
+
+	return 0;
+}
+
+
+static inline int
+__atmel_pwm_config_period_ticks(struct pwm_channel *p,
+				struct pwm_channel_config *c)
+{
+	u32 cmr, cprd, cpre;
+
+	cpre = fls(c->period_ticks);
+	if (cpre < 16)
+		cpre = 0;
+	else {
+		cpre -= 15;
+		if (cpre > 10)
+			return -EINVAL;
+	}
+
+	cmr = pwmc_chan_readl(p, PWMC_CMR);
+	cmr &= ~PWMC_CMR_CPRE_MASK;
+	cmr |= cpre;
+
+	cprd = c->period_ticks >> cpre;
+
+	pwmc_chan_writel(p, PWMC_CMR, cmr);
+	pwmc_chan_writel(p, PWMC_CPRD, cprd);
+	p->period_ticks = c->period_ticks;
+
+	pr_debug("%s:%d period_ticks = %lu cprd = %x cpre = %x\n",
+		 p->pwm->bus_id, p->chan, p->period_ticks, cprd, cpre);
+
+	return 0;
+}
+
+
+
+static int
+atmel_pwm_config_nosleep(struct pwm_channel *p,
+			 struct pwm_channel_config *c)
+{
+	int ret = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&p->lock, flags);
+
+	switch (c->config_mask) {
+
+	case PWM_CONFIG_DUTY_TICKS:
+		__atmel_pwm_config_duty_ticks(p, c);
+		break;
+
+	case PWM_CONFIG_STOP:
+		__atmel_pwm_stop(p);
+		pr_debug("%s:%d stop\n", p->pwm->bus_id, p->chan);
+		break;
+
+	case PWM_CONFIG_START:
+		__atmel_pwm_start(p);
+		pr_debug("%s:%d start\n", p->pwm->bus_id, p->chan);
+		break;
+
+	case PWM_CONFIG_POLARITY:
+		__atmel_pwm_config_polarity(p, c);
+		break;
+
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	spin_unlock_irqrestore(&p->lock, flags);
+	return ret;
+}
+
+
+static int
+atmel_pwm_stop_sync(struct pwm_channel *p)
+{
+	struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+	int ret;
+	int was_on = __atmel_pwm_is_on(p);
+
+	if (was_on) {
+		do {
+			init_completion(&p->complete);
+			set_bit(FLAG_STOP, &p->flags);
+			pwmc_writel(ap, PWMC_IER, 1 << p->chan);
+
+			pr_debug("%s:%d waiting on stop_sync completion...\n",
+				 p->pwm->bus_id, p->chan);
+
+			ret = wait_for_completion_interruptible(&p->complete);
+
+			pr_debug("%s:%d stop_sync complete (%d)\n",
+				 p->pwm->bus_id, p->chan, ret);
+
+			if (ret)
+				return ret;
+		} while (p->flags & BIT(FLAG_STOP));
+	}
+
+	pr_debug("%s:%d stop_sync returning %d\n",
+		 p->pwm->bus_id, p->chan, was_on);
+
+	return was_on;
+}
+
+
+static int
+atmel_pwm_config(struct pwm_channel *p,
+		 struct pwm_channel_config *c)
+{
+	int was_on = 0;
+
+	if (p->pwm->config_nosleep) {
+		if (!p->pwm->config_nosleep(p, c))
+			return 0;
+	}
+
+	might_sleep();
+
+	pr_debug("%s:%d config_mask %x\n",
+		 p->pwm->bus_id, p->chan, c->config_mask);
+
+	was_on = atmel_pwm_stop_sync(p);
+	if (was_on < 0)
+		return was_on;
+
+	if (c->config_mask & PWM_CONFIG_PERIOD_TICKS) {
+		__atmel_pwm_config_period_ticks(p, c);
+		if (!(c->config_mask & PWM_CONFIG_DUTY_TICKS)) {
+			struct pwm_channel_config d = {
+				.config_mask = PWM_CONFIG_DUTY_TICKS,
+				.duty_ticks = p->duty_ticks,
+			};
+			__atmel_pwm_config_duty_ticks(p, &d);
+		}
+	}
+
+	if (c->config_mask & PWM_CONFIG_DUTY_TICKS)
+		__atmel_pwm_config_duty_ticks(p, c);
+
+	if (c->config_mask & PWM_CONFIG_POLARITY)
+		__atmel_pwm_config_polarity(p, c);
+
+	if ((c->config_mask & PWM_CONFIG_START)
+	    || (was_on && !(c->config_mask & PWM_CONFIG_STOP)))
+		__atmel_pwm_start(p);
+
+	return 0;
+}
+
+
+static void
+__atmel_pwm_set_callback(struct pwm_channel *p,
+			 pwm_callback_t callback)
+{
+	struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+
+	p->callback = callback;
+	pwmc_writel(ap, p->callback ? PWMC_IER : PWMC_IDR, 1 << p->chan);
+	pr_debug("%s:%d set_callback %p\n", p->pwm->bus_id, p->chan, callback);
+}
+
+
+static int
+atmel_pwm_set_callback(struct pwm_channel *p,
+		       pwm_callback_t callback)
+{
+	struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+	unsigned long flags;
+
+	spin_lock_irqsave(&ap->lock, flags);
+	__atmel_pwm_set_callback(p, callback);
+	spin_unlock_irqrestore(&ap->lock, flags);
+
+	return 0;
+}
+
+
+static int
+atmel_pwm_request(struct pwm_channel *p)
+{
+	struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+	unsigned long flags;
+
+	spin_lock_irqsave(&p->lock, flags);
+	clk_enable(ap->clk);
+	p->tick_hz = clk_get_rate(ap->clk);
+	__atmel_pwm_unsynchronize(p, NULL);
+	__atmel_pwm_stop(p);
+	spin_unlock_irqrestore(&p->lock, flags);
+
+	return 0;
+}
+
+
+static void
+atmel_pwm_free(struct pwm_channel *p)
+{
+	struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+	clk_disable(ap->clk);
+}
+
+
+static irqreturn_t
+atmel_pwmc_irq(int irq, void *data)
+{
+	struct atmel_pwm *ap = data;
+	struct pwm_channel *p;
+	u32 isr;
+	int chid;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ap->lock, flags);
+
+	isr = pwmc_readl(ap, PWMC_ISR);
+	for (chid = 0; isr; chid++, isr >>= 1) {
+		p = &ap->pwm.channels[chid];
+		if (isr & 1) {
+			if (p->callback)
+				p->callback(p);
+			if (p->flags & BIT(FLAG_STOP)) {
+				__atmel_pwm_stop(p);
+				clear_bit(FLAG_STOP, &p->flags);
+			}
+			complete_all(&p->complete);
+		}
+	}
+
+	spin_unlock_irqrestore(&ap->lock, flags);
+
+	return IRQ_HANDLED;
+}
+
+
+static int __init
+atmel_pwmc_probe(struct platform_device *pdev)
+{
+	struct atmel_pwm *ap;
+	struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	int ret = 0;
+
+	ap = kzalloc(sizeof(*ap), GFP_KERNEL);
+	if (!ap) {
+		ret = -ENOMEM;
+		goto err_atmel_pwm_alloc;
+	}
+
+	spin_lock_init(&ap->lock);
+	platform_set_drvdata(pdev, ap);
+
+	ap->pwm.bus_id = pdev->dev.bus_id;
+
+	ap->pwm.nchan = 4; /* TODO: true only for SAM9263 and AP7000 */
+	ap->ccnt_mask = 0xffffUL; /* TODO: true only for SAM9263 */
+
+	ap->sync_mask = kzalloc(ap->pwm.nchan * sizeof(u32), GFP_KERNEL);
+	if (!ap->sync_mask) {
+		ret = -ENOMEM;
+		goto err_alloc_sync_masks;
+	}
+
+	ap->pwm.owner = THIS_MODULE;
+	ap->pwm.request = atmel_pwm_request;
+	ap->pwm.free = atmel_pwm_free;
+	ap->pwm.config_nosleep = atmel_pwm_config_nosleep;
+	ap->pwm.config = atmel_pwm_config;
+	ap->pwm.synchronize = atmel_pwm_synchronize;
+	ap->pwm.unsynchronize = atmel_pwm_unsynchronize;
+	ap->pwm.set_callback = atmel_pwm_set_callback;
+
+	ap->clk = clk_get(&pdev->dev, "pwm_clk");
+	if (IS_ERR(ap->clk)) {
+		pr_info("%s: clk_get error %ld\n",
+			ap->pwm.bus_id, PTR_ERR(ap->clk));
+		ret = -ENODEV;
+		goto err_clk_get;
+	}
+
+	ap->iobase = ioremap_nocache(r->start, r->end - r->start + 1);
+	if (!ap->iobase) {
+		ret = -ENODEV;
+		goto err_ioremap;
+	}
+
+	clk_enable(ap->clk);
+	pwmc_writel(ap, PWMC_DIS, -1);
+	pwmc_writel(ap, PWMC_IDR, -1);
+	clk_disable(ap->clk);
+
+	ap->irq = platform_get_irq(pdev, 0);
+	if (ap->irq != -ENXIO) {
+		ret = request_irq(ap->irq, atmel_pwmc_irq, 0,
+				  ap->pwm.bus_id, ap);
+		if (ret)
+			goto err_request_irq;
+	}
+
+	ret = pwm_register(&ap->pwm);
+	if (ret)
+		goto err_pwm_register;
+
+	return 0;
+
+err_pwm_register:
+	if (ap->irq != -ENXIO)
+		free_irq(ap->irq, ap);
+err_request_irq:
+	iounmap(ap->iobase);
+err_ioremap:
+	clk_put(ap->clk);
+err_clk_get:
+	platform_set_drvdata(pdev, NULL);
+err_alloc_sync_masks:
+	kfree(ap);
+err_atmel_pwm_alloc:
+	return ret;
+}
+
+
+static int __devexit
+atmel_pwmc_remove(struct platform_device *pdev)
+{
+	struct atmel_pwm *ap = platform_get_drvdata(pdev);
+	int ret;
+
+	/* TODO: what can we do if this fails? */
+	ret = pwm_unregister(&ap->pwm);
+
+	clk_enable(ap->clk);
+	pwmc_writel(ap, PWMC_IDR, -1);
+	pwmc_writel(ap, PWMC_DIS, -1);
+	clk_disable(ap->clk);
+
+	if (ap->irq != -ENXIO)
+		free_irq(ap->irq, ap);
+
+	clk_put(ap->clk);
+	iounmap(ap->iobase);
+
+	kfree(ap);
+
+	return 0;
+}
+
+
+static struct platform_driver atmel_pwm_driver = {
+	.driver = {
+		.name = "atmel_pwmc",
+		.owner = THIS_MODULE,
+	},
+	.probe = atmel_pwmc_probe,
+	.remove = __devexit_p(atmel_pwmc_remove),
+};
+
+
+static int __init atmel_pwm_init(void)
+{
+	return platform_driver_register(&atmel_pwm_driver);
+}
+module_init(atmel_pwm_init);
+
+
+static void atmel_pwm_exit(void)
+{
+	platform_driver_unregister(&atmel_pwm_driver);
+}
+module_exit(atmel_pwm_exit);
+
+
+MODULE_AUTHOR("Bill Gatliff <bgat@billgatliff.com>");
+MODULE_DESCRIPTION("Driver for Atmel PWMC peripheral");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:atmel_pwmc");
-- 
1.5.6.5

^ permalink raw reply related


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