Message ID | 1365093431-30621-5-git-send-email-t.figa@samsung.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Hi Tomasz, On Thu, Apr 04, 2013 at 06:37:01PM +0200, Tomasz Figa wrote: > This patch adds master driver for PWM/timer block available on many > Samsung SoCs providing clocksource and PWM output capabilities. > > Signed-off-by: Tomasz Figa <t.figa@samsung.com> > Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> > --- > .../devicetree/bindings/pwm/pwm-samsung.txt | 37 ++ > drivers/clocksource/Kconfig | 1 + > drivers/mfd/Kconfig | 3 + > drivers/mfd/Makefile | 1 + > drivers/mfd/samsung-pwm.c | 439 +++++++++++++++++++++ > drivers/pwm/Kconfig | 1 + > include/linux/mfd/samsung-pwm.h | 49 +++ > include/linux/platform_data/samsung-pwm.h | 28 ++ > 8 files changed, 559 insertions(+) > create mode 100644 Documentation/devicetree/bindings/pwm/pwm-samsung.txt > create mode 100644 drivers/mfd/samsung-pwm.c > create mode 100644 include/linux/mfd/samsung-pwm.h > create mode 100644 include/linux/platform_data/samsung-pwm.h Why is that an MFD driver, and why aren't you using the PWM APIs for it ? Also, you probably want to look at using the regmap APIs for your IO. Cheers, Samuel.
Hi Samuel, On Friday 05 of April 2013 18:39:58 Samuel Ortiz wrote: > Hi Tomasz, > > On Thu, Apr 04, 2013 at 06:37:01PM +0200, Tomasz Figa wrote: > > This patch adds master driver for PWM/timer block available on many > > Samsung SoCs providing clocksource and PWM output capabilities. > > > > Signed-off-by: Tomasz Figa <t.figa@samsung.com> > > Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> > > --- > > > > .../devicetree/bindings/pwm/pwm-samsung.txt | 37 ++ > > drivers/clocksource/Kconfig | 1 + > > drivers/mfd/Kconfig | 3 + > > drivers/mfd/Makefile | 1 + > > drivers/mfd/samsung-pwm.c | 439 > > +++++++++++++++++++++ drivers/pwm/Kconfig > > | 1 + > > include/linux/mfd/samsung-pwm.h | 49 +++ > > include/linux/platform_data/samsung-pwm.h | 28 ++ > > 8 files changed, 559 insertions(+) > > create mode 100644 Documentation/devicetree/bindings/pwm/pwm-samsung.txt > > create mode 100644 drivers/mfd/samsung-pwm.c > > create mode 100644 include/linux/mfd/samsung-pwm.h > > create mode 100644 include/linux/platform_data/samsung-pwm.h > > Why is that an MFD driver, and why aren't you using the PWM APIs for it ? > Also, you probably want to look at using the regmap APIs for your IO. The case of Samsung PWM timers is rather complicated. It is a hardware block that can be used at the same time to generate PWM signal and as a system clocksource. There was a discussion on how to solve the problem of sharing the hardware: http://thread.gmane.org/gmane.linux.kernel.samsung-soc/16480/focus=16500 (see the linked post and replies to it). Best regards,
On Friday 05 April 2013, Tomasz Figa wrote: > On Friday 05 of April 2013 18:39:58 Samuel Ortiz wrote: > > Hi Tomasz, > > > > On Thu, Apr 04, 2013 at 06:37:01PM +0200, Tomasz Figa wrote: > > > This patch adds master driver for PWM/timer block available on many > > > Samsung SoCs providing clocksource and PWM output capabilities. > > > Why is that an MFD driver, and why aren't you using the PWM APIs for it ? > > Also, you probably want to look at using the regmap APIs for your IO. > > The case of Samsung PWM timers is rather complicated. It is a hardware block > that can be used at the same time to generate PWM signal and as a system > clocksource. > > There was a discussion on how to solve the problem of sharing the hardware: > http://thread.gmane.org/gmane.linux.kernel.samsung-soc/16480/focus=16500 > (see the linked post and replies to it). > I don't think anyone ever suggested using a private API though. I think it's ok if the driver lives in drivers/mfd when it doesn't fit anywhere else easily, but you should really register it to the pwm subsystem to expose that functionality, not export functions that can be used by a pwm shim driver, which even seems to be missing from the series. Arnd
On Friday 05 of April 2013 19:05:24 Arnd Bergmann wrote: > On Friday 05 April 2013, Tomasz Figa wrote: > > On Friday 05 of April 2013 18:39:58 Samuel Ortiz wrote: > > > Hi Tomasz, > > > > > > On Thu, Apr 04, 2013 at 06:37:01PM +0200, Tomasz Figa wrote: > > > > This patch adds master driver for PWM/timer block available on many > > > > Samsung SoCs providing clocksource and PWM output capabilities. > > > > > > Why is that an MFD driver, and why aren't you using the PWM APIs for it > > > ? > > > Also, you probably want to look at using the regmap APIs for your IO. > > > > The case of Samsung PWM timers is rather complicated. It is a hardware > > block that can be used at the same time to generate PWM signal and as a > > system clocksource. > > > > There was a discussion on how to solve the problem of sharing the > > hardware: > > http://thread.gmane.org/gmane.linux.kernel.samsung-soc/16480/focus=16500 > > (see the linked post and replies to it). > > I don't think anyone ever suggested using a private API though. I must have gotten the last paragraph of http://article.gmane.org/gmane.linux.kernel.samsung-soc/16638 wrong then. For me it seemed like the timer driver would expose a private API to the PWM driver. > I think > it's ok if the driver lives in drivers/mfd when it doesn't fit anywhere > else easily, but you should really register it to the pwm subsystem to > expose that functionality, not export functions that can be used by > a pwm shim driver, which even seems to be missing from the series. Anyway, using PWM API in a clocksource driver that needs to be running very early (before any initcalls get called) seems a bit weird for me, especially when PWM API doesn't provide such fine grained control over PWM timers that is required in the clocksource drivers. Best regards,
On Friday 05 April 2013, Tomasz Figa wrote: > > I don't think anyone ever suggested using a private API though. > > I must have gotten the last paragraph of > http://article.gmane.org/gmane.linux.kernel.samsung-soc/16638 > wrong then. For me it seemed like the timer driver would expose a private API > to the PWM driver. Yes, sorry I wasn't clear enough. I meant a register-level interface exposed from the base driver, not a high-level interface like you did. > > I think > > it's ok if the driver lives in drivers/mfd when it doesn't fit anywhere > > else easily, but you should really register it to the pwm subsystem to > > expose that functionality, not export functions that can be used by > > a pwm shim driver, which even seems to be missing from the series. > > Anyway, using PWM API in a clocksource driver that needs to be running very > early (before any initcalls get called) seems a bit weird for me, especially > when PWM API doesn't provide such fine grained control over PWM timers that is > required in the clocksource drivers. Exactly, that's why you should have a regular PWM driver that gets loaded at a convenient time and just uses an interface exported by the base driver to access the common registers. Arnd
On Friday 05 of April 2013 21:54:21 Arnd Bergmann wrote: > On Friday 05 April 2013, Tomasz Figa wrote: > > > I don't think anyone ever suggested using a private API though. > > > > I must have gotten the last paragraph of > > http://article.gmane.org/gmane.linux.kernel.samsung-soc/16638 > > wrong then. For me it seemed like the timer driver would expose a > > private API to the PWM driver. > > Yes, sorry I wasn't clear enough. I meant a register-level interface > exposed from the base driver, not a high-level interface like you > did. I'm not sure what you mean by a register-level interface. Something like samsung_pwm_update_reg(reg, mask, val), which modifies bitfields according to the mask and value with appropriate synchronization? If yes, this solves only the problem of access to shared registers. The other problems that remain: - negotiation of PWM channels to use for clock source and clock events, because each board can use different channels for PWM outputs, - code duplication caused by both of the drivers doing mostly the same things and or having to parse the same data from device tree, - both non-DT and DT platforms must be supported, - how to keep the ability to load PWM driver as a module (or not load it at all when PWM is not used on particular board), while retaining everything that is needed for the clocksource driver in kernel, - some platforms can't use PWM timers as system clocksources, while on others this is the only timekeeping hardware available. > > > I think > > > it's ok if the driver lives in drivers/mfd when it doesn't fit > > > anywhere > > > else easily, but you should really register it to the pwm subsystem > > > to > > > expose that functionality, not export functions that can be used by > > > a pwm shim driver, which even seems to be missing from the series. > > > > Anyway, using PWM API in a clocksource driver that needs to be running > > very early (before any initcalls get called) seems a bit weird for > > me, especially when PWM API doesn't provide such fine grained control > > over PWM timers that is required in the clocksource drivers. > > Exactly, that's why you should have a regular PWM driver that gets > loaded at a convenient time and just uses an interface exported by the > base driver to access the common registers. Well, this is basically what my patches do, except that the PWM driver isn't reworked to use the base driver yet. The private API exported to the samsung-time and pwm-samsung drivers isn't maybe the most fortunate one, but it clearly has the advantage of solving all the problems I mentioned before. Same goes for calling this an MFD driver. It doesn't even use the MFD core, but there seems to be no better place to put it. Maybe drivers/platform/arm would be better, if it existed, just as currently available drivers/platform/x86? Best regards, Tomasz
On Saturday 06 of April 2013 00:24:18 Tomasz Figa wrote: > On Friday 05 of April 2013 21:54:21 Arnd Bergmann wrote: > > On Friday 05 April 2013, Tomasz Figa wrote: > > > > I don't think anyone ever suggested using a private API though. > > > > > > I must have gotten the last paragraph of > > > http://article.gmane.org/gmane.linux.kernel.samsung-soc/16638 > > > wrong then. For me it seemed like the timer driver would expose a > > > private API to the PWM driver. > > > > Yes, sorry I wasn't clear enough. I meant a register-level interface > > exposed from the base driver, not a high-level interface like you > > did. > > I'm not sure what you mean by a register-level interface. Something like > samsung_pwm_update_reg(reg, mask, val), which modifies bitfields > according to the mask and value with appropriate synchronization? If > yes, this solves only the problem of access to shared registers. > > The other problems that remain: > > - negotiation of PWM channels to use for clock source and clock events, > because each board can use different channels for PWM outputs, > > - code duplication caused by both of the drivers doing mostly the same > things and or having to parse the same data from device tree, > > - both non-DT and DT platforms must be supported, > > - how to keep the ability to load PWM driver as a module (or not load it > at all when PWM is not used on particular board), while retaining > everything that is needed for the clocksource driver in kernel, > > - some platforms can't use PWM timers as system clocksources, while on > others this is the only timekeeping hardware available. > Hmm. Does anybody have an idea of a better way of implementing this PWM timer support, which solves the above problems? This series is a dependency for moving Universal C210 board to DT-based description and it's already almost out of time to get this included for 3.10... Best regards, Tomasz > > > > I think > > > > it's ok if the driver lives in drivers/mfd when it doesn't fit > > > > anywhere > > > > else easily, but you should really register it to the pwm > > > > subsystem > > > > to > > > > expose that functionality, not export functions that can be used > > > > by > > > > a pwm shim driver, which even seems to be missing from the series. > > > > > > Anyway, using PWM API in a clocksource driver that needs to be > > > running > > > very early (before any initcalls get called) seems a bit weird for > > > me, especially when PWM API doesn't provide such fine grained > > > control > > > over PWM timers that is required in the clocksource drivers. > > > > Exactly, that's why you should have a regular PWM driver that gets > > loaded at a convenient time and just uses an interface exported by the > > base driver to access the common registers. > > Well, this is basically what my patches do, except that the PWM driver > isn't reworked to use the base driver yet. > > The private API exported to the samsung-time and pwm-samsung drivers > isn't maybe the most fortunate one, but it clearly has the advantage of > solving all the problems I mentioned before. > > Same goes for calling this an MFD driver. It doesn't even use the MFD > core, but there seems to be no better place to put it. Maybe > drivers/platform/arm would be better, if it existed, just as currently > available drivers/platform/x86? > > Best regards, > Tomasz
On Monday 08 April 2013, Tomasz Figa wrote: > On Saturday 06 of April 2013 00:24:18 Tomasz Figa wrote: > > On Friday 05 of April 2013 21:54:21 Arnd Bergmann wrote: > > > On Friday 05 April 2013, Tomasz Figa wrote: > > > > I'm not sure what you mean by a register-level interface. Something like > > samsung_pwm_update_reg(reg, mask, val), which modifies bitfields > > according to the mask and value with appropriate synchronization? If > > yes, this solves only the problem of access to shared registers. > > > > The other problems that remain: > > > > - negotiation of PWM channels to use for clock source and clock events, > > because each board can use different channels for PWM outputs, > > > > - code duplication caused by both of the drivers doing mostly the same > > things and or having to parse the same data from device tree, > > > > - both non-DT and DT platforms must be supported, > > > > - how to keep the ability to load PWM driver as a module (or not load it > > at all when PWM is not used on particular board), while retaining > > everything that is needed for the clocksource driver in kernel, > > > > - some platforms can't use PWM timers as system clocksources, while on > > others this is the only timekeeping hardware available. > > > > Hmm. Does anybody have an idea of a better way of implementing this PWM > timer support, which solves the above problems? > > This series is a dependency for moving Universal C210 board to DT-based > description and it's already almost out of time to get this included for > 3.10... > Sorry for not replying earlier. My idea for the register level interface was to create a platform_device for each PWM, e.g. using the mfd_cell infrastructure. You can then pass a "struct regmap" as the platform data for each child of the timer node, and all the DT handling code can stay in the parent driver. Arnd
On Thu, Apr 11, 2013 at 12:35:47AM +0200, Arnd Bergmann wrote: > Sorry for not replying earlier. My idea for the register level interface > was to create a platform_device for each PWM, e.g. using the mfd_cell > infrastructure. You can then pass a "struct regmap" as the platform > data for each child of the timer node, and all the DT handling code > can stay in the parent driver. Dunno if it helps or not but there's also dev_get_regmap() if you're passing the struct device around or can fish one out of thhe parent.
On Thursday 11 of April 2013 00:35:47 Arnd Bergmann wrote: > On Monday 08 April 2013, Tomasz Figa wrote: > > On Saturday 06 of April 2013 00:24:18 Tomasz Figa wrote: > > > On Friday 05 of April 2013 21:54:21 Arnd Bergmann wrote: > > > > On Friday 05 April 2013, Tomasz Figa wrote: > > > I'm not sure what you mean by a register-level interface. Something like > > > samsung_pwm_update_reg(reg, mask, val), which modifies bitfields > > > according to the mask and value with appropriate synchronization? If > > > yes, this solves only the problem of access to shared registers. > > > > > > The other problems that remain: > > > > > > - negotiation of PWM channels to use for clock source and clock events, > > > > > > because each board can use different channels for PWM outputs, > > > > > > - code duplication caused by both of the drivers doing mostly the same > > > > > > things and or having to parse the same data from device tree, > > > > > > - both non-DT and DT platforms must be supported, > > > > > > - how to keep the ability to load PWM driver as a module (or not load it > > > at all when PWM is not used on particular board), while retaining > > > everything that is needed for the clocksource driver in kernel, > > > > > > - some platforms can't use PWM timers as system clocksources, while on > > > > > > others this is the only timekeeping hardware available. > > > > Hmm. Does anybody have an idea of a better way of implementing this PWM > > timer support, which solves the above problems? > > > > This series is a dependency for moving Universal C210 board to DT-based > > description and it's already almost out of time to get this included for > > 3.10... > > Sorry for not replying earlier. My idea for the register level interface > was to create a platform_device for each PWM, e.g. using the mfd_cell > infrastructure. You can then pass a "struct regmap" as the platform > data for each child of the timer node, and all the DT handling code > can stay in the parent driver. Hmm. As I said, I'm completely fine with using a regmap for sharing registers between subdrivers. However the clocksource can not be registered as an MFD cell, because it's needed very early. I can imagine a solution alternative to my original one, where the MFD cells would be registered from the clocksource driver. This would mean that platforms that don't need (and can't use) the PWM clocksource would have to enable the driver anyway. Another thing is that I don't see a need to create one cell per PWM channel. The PWM core is designed in a way that supports multiple channels per PWM controller and so is the generic PWM DT specifier (<&controller channel period flags>), so I'd rather see a single cell for all PWM channels. So, to summarize this alternative concept: - two drivers: 1) clocksource driver - registering clocksource and PWM MFD cell 2) PWM driver - handling the PWM MFD cell - both drivers would share registers using a regmap with custom lock/unlock callbacks (using spin_lock_irqsave/spin_unlock_irqrestore, because the clocksource needs to access PWM registers in IRQ context) - the clocksource/master driver would have a samsung_time_init function which would set up the driver early and initialize the clocksource - a platform driver would be registered by the clocksource/master driver which would register the PWM MFD cell in its probe - MFD cell platform data would contain struct regmap * and variant data (parsed from DT or received from platform code - as in my original version) This should indeed work, assuming that I find a solution to make clocksource_of_init not initialize the PWM clocksource (from PWM DT node) on platforms that can't use it. Best regards,
On Thursday 11 April 2013, Tomasz Figa wrote: > On Thursday 11 of April 2013 00:35:47 Arnd Bergmann wrote: > > On Monday 08 April 2013, Tomasz Figa wrote: > > > On Saturday 06 of April 2013 00:24:18 Tomasz Figa wrote: > > > > On Friday 05 of April 2013 21:54:21 Arnd Bergmann wrote: > > > > > On Friday 05 April 2013, Tomasz Figa wrote: > > > > Sorry for not replying earlier. My idea for the register level interface > > was to create a platform_device for each PWM, e.g. using the mfd_cell > > infrastructure. You can then pass a "struct regmap" as the platform > > data for each child of the timer node, and all the DT handling code > > can stay in the parent driver. > > Hmm. As I said, I'm completely fine with using a regmap for sharing registers > between subdrivers. However the clocksource can not be registered as an MFD > cell, because it's needed very early. > > I can imagine a solution alternative to my original one, where the MFD cells > would be registered from the clocksource driver. This would mean that > platforms that don't need (and can't use) the PWM clocksource would have to > enable the driver anyway. Sounds ok to me. The main part I want to avoid is having two separate device nodes in the DT for one physical device, but there are multiple ways to get there. > Another thing is that I don't see a need to create one cell per PWM channel. > The PWM core is designed in a way that supports multiple channels per PWM > controller and so is the generic PWM DT specifier (<&controller channel period > flags>), so I'd rather see a single cell for all PWM channels. Sure, no problem with that. > So, to summarize this alternative concept: > - two drivers: > 1) clocksource driver - registering clocksource and PWM MFD cell > 2) PWM driver - handling the PWM MFD cell Ok. Don't be too tied to the MFD concept though if it causes problems. In many cases calling platform_device_register_resndata or similar is actually easier than MFD, especially if you only want a single child device. Either way is fine though. > - both drivers would share registers using a regmap with custom lock/unlock > callbacks (using spin_lock_irqsave/spin_unlock_irqrestore, because the > clocksource needs to access PWM registers in IRQ context) Ok. > - the clocksource/master driver would have a samsung_time_init function which > would set up the driver early and initialize the clocksource Right. If you want to use the clocksource_of_init() infratstructure for this (which is probably a good idea), please base your series on top of the clksrc/cleanup branch in arm-soc. > - a platform driver would be registered by the clocksource/master driver > which would register the PWM MFD cell in its probe. This seems unnecessary, if the only purpose of the platform driver is to export the MFD cell. In that case, I would actually suggest a simpler model where the PWM driver registers the main platform driver directly and calls a function exported by the clocksource driver to get the regmap structure and anything else it may need (possibly initializing the driver in the case where it is not used as the clocksource but only provides the registers for PWM). > - MFD cell platform data would contain struct regmap * and variant data > (parsed from DT or received from platform code - as in my original version) Yes, either the MFD cell, or the exported symbol, whichever ends up easier. > This should indeed work, assuming that I find a solution to make > clocksource_of_init not initialize the PWM clocksource (from PWM DT node) on > platforms that can't use it. Right. If anything else comes up, I'd suggest we discuss it on IRC (#armlinux on irc.freenode.net) tomorrow for faster round-trip, or I can call you on the phone if you like. I'm available all day tomorrow, just send me your (landline) phone number by private email. Arnd.
diff --git a/Documentation/devicetree/bindings/pwm/pwm-samsung.txt b/Documentation/devicetree/bindings/pwm/pwm-samsung.txt new file mode 100644 index 0000000..8ed4c11 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/pwm-samsung.txt @@ -0,0 +1,37 @@ +* Samsung PWM timers + +Samsung SoCs contain PWM timer blocks which can be used for system clock source +and clock event timers, as well as to drive SoC outputs with PWM signal. Each +PWM timer block provides 5 PWM channels (not all of them can drive physical +outputs - see SoC and board manual). + +Be aware that this driver supports clock event only on CPU 0. It can +provide SMP support together with ARM dummy_timer, but only in periodic tick +mode. + +Required properties: +- compatible : should be one of following: + samsung,s3c2410-pwm - for 16-bit timers present on S3C24xx SoCs + samsung,s3c6400-pwm - for 32-bit timers present on S3C64xx SoCs + samsung,s5p6440-pwm - for 32-bit timers present on S5P64x0 SoCs + samsung,s5pc100-pwm - for 32-bit timers present on S5PC100, S5PV210, + Exynos4210, Exynos4x12 and Exynos5250 SoCs +- reg: base address and size of register area +- interrupts: list of timer interrupts (one interrupt per timer, starting at + timer 0) +- #pwm-cells: number of cells used for PWM specifier - must be 2 + +Optional properties: +- samsung,pwm-outputs: list of PWM channels used as PWM outputs on particular + platform - an array of up to 5 elements being indices of PWM channels + (from 0 to 4), the order does not matter. + +Example: + pwm@7f006000 { + compatible = "samsung,s3c6400-pwm"; + reg = <0x7f006000 0x1000>; + interrupt-parent = <&vic0>; + interrupts = <23>, <24>, <25>, <27>, <28>; + samsung,pwm-outputs = <0>, <1>; + #pwm-cells = <2>; + } diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index dd20f6a..a69a5b7 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -29,6 +29,7 @@ config SAMSUNG_HRT bool depends on PLAT_SAMSUNG select SAMSUNG_DEV_PWM + select MFD_SAMSUNG_PWM help Use the high resolution timer support on Samsung platforms. diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 3ab3a11..c25425c 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1015,6 +1015,9 @@ config MFD_PM8XXX_IRQ This is required to use certain other PM 8xxx features, such as GPIO and MPP. +config MFD_SAMSUNG_PWM + bool + config TPS65911_COMPARATOR tristate diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index b90409c..513d9c9 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -145,6 +145,7 @@ obj-$(CONFIG_MFD_RC5T583) += rc5t583.o rc5t583-irq.o obj-$(CONFIG_MFD_SEC_CORE) += sec-core.o sec-irq.o obj-$(CONFIG_MFD_SYSCON) += syscon.o obj-$(CONFIG_MFD_LM3533) += lm3533-core.o lm3533-ctrlbank.o +obj-$(CONFIG_MFD_SAMSUNG_PWM) += samsung-pwm.o obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-config.o vexpress-sysreg.o obj-$(CONFIG_MFD_RETU) += retu-mfd.o obj-$(CONFIG_MFD_AS3711) += as3711.o diff --git a/drivers/mfd/samsung-pwm.c b/drivers/mfd/samsung-pwm.c new file mode 100644 index 0000000..ece426e --- /dev/null +++ b/drivers/mfd/samsung-pwm.c @@ -0,0 +1,439 @@ +/* + * Samsung PWM/timers MFD driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * + * Author: Tomasz Figa <t.figa@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/err.h> +#include <linux/io.h> +#include <linux/list.h> +#include <linux/mfd/samsung-pwm.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/platform_data/samsung-pwm.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#define REG_TCFG0 0x00 +#define REG_TCFG1 0x04 +#define REG_TCON 0x08 + +#define REG_TCNTB(chan) (0x0c + 12 * (chan)) +#define REG_TCMPB(chan) (0x10 + 12 * (chan)) + +#define TCON_START(chan) (1 << (4 * (chan) + 0)) +#define TCON_MANUALUPDATE(chan) (1 << (4 * (chan) + 1)) +#define TCON_INVERT(chan) (1 << (4 * (chan) + 2)) +#define TCON_AUTORELOAD(chan) (1 << (4 * (chan) + 3)) + +#define TCFG0_PRESCALER_MASK 0xff +#define TCFG0_PRESCALER1_SHIFT 8 + +#define TCFG1_SHIFT(x) ((x) * 4) +#define TCFG1_MUX_MASK 0xf + +struct samsung_pwm_drvdata { + struct samsung_pwm pwm; + struct platform_device *pdev; + struct resource resource; + struct list_head list; + spinlock_t slock; + unsigned long request_mask; +}; + +static LIST_HEAD(pwm_list); + +static inline struct samsung_pwm_drvdata *to_drvdata(struct samsung_pwm *pwm) +{ + return container_of(pwm, struct samsung_pwm_drvdata, pwm); +} + +void samsung_pwm_start(struct samsung_pwm *pwm, + unsigned int channel, bool periodic) +{ + struct samsung_pwm_drvdata *drvdata = to_drvdata(pwm); + unsigned long flags; + u32 reg; + + if (channel > 0) + ++channel; + + spin_lock_irqsave(&drvdata->slock, flags); + + reg = readl(pwm->base + REG_TCON); + reg &= ~TCON_MANUALUPDATE(channel); + reg |= TCON_START(channel); + if (periodic) + reg |= TCON_AUTORELOAD(channel); + else + reg &= ~TCON_AUTORELOAD(channel); + writel(reg, pwm->base + REG_TCON); + + spin_unlock_irqrestore(&drvdata->slock, flags); +} +EXPORT_SYMBOL(samsung_pwm_start); + +void samsung_pwm_stop(struct samsung_pwm *pwm, unsigned int channel) +{ + struct samsung_pwm_drvdata *drvdata = to_drvdata(pwm); + unsigned long flags; + u32 reg; + + if (channel > 0) + ++channel; + + spin_lock_irqsave(&drvdata->slock, flags); + + reg = readl(pwm->base + REG_TCON); + reg &= ~TCON_START(channel); + writel(reg, pwm->base + REG_TCON); + + spin_unlock_irqrestore(&drvdata->slock, flags); +} +EXPORT_SYMBOL(samsung_pwm_stop); + +void samsung_pwm_setup(struct samsung_pwm *pwm, unsigned int channel, + u32 tcmp, u32 tcnt) +{ + struct samsung_pwm_drvdata *drvdata = to_drvdata(pwm); + unsigned long flags; + u32 reg; + + writel(tcnt, pwm->base + REG_TCNTB(channel)); + writel(tcmp, pwm->base + REG_TCMPB(channel)); + + if (channel > 0) + ++channel; + + spin_lock_irqsave(&drvdata->slock, flags); + + reg = readl(pwm->base + REG_TCON); + reg &= ~TCON_START(channel); + reg |= TCON_MANUALUPDATE(channel); + writel(reg, pwm->base + REG_TCON); + + spin_unlock_irqrestore(&drvdata->slock, flags); +} +EXPORT_SYMBOL(samsung_pwm_setup); + +void samsung_pwm_set_flags(struct samsung_pwm *pwm, + unsigned int channel, u32 pwm_flags) +{ + struct samsung_pwm_drvdata *drvdata = to_drvdata(pwm); + unsigned long flags; + u32 reg; + + if (channel > 0) + ++channel; + + spin_lock_irqsave(&drvdata->slock, flags); + + reg = readl(pwm->base + REG_TCON); + + if (pwm_flags & SAMSUNG_PWM_INVERT) + reg |= TCON_INVERT(channel); + else + reg &= ~TCON_INVERT(channel); + + writel(reg, pwm->base + REG_TCON); + + spin_unlock_irqrestore(&drvdata->slock, flags); +} +EXPORT_SYMBOL(samsung_pwm_set_flags); + +void samsung_pwm_set_prescale(struct samsung_pwm *pwm, + unsigned int channel, u16 prescale) +{ + struct samsung_pwm_drvdata *drvdata = to_drvdata(pwm); + unsigned long flags; + u8 shift = 0; + u32 reg; + + if (channel >= 2) + shift = TCFG0_PRESCALER1_SHIFT; + + spin_lock_irqsave(&drvdata->slock, flags); + + reg = readl(pwm->base + REG_TCFG0); + reg &= ~(TCFG0_PRESCALER_MASK << shift); + reg |= (prescale - 1) << shift; + writel(reg, pwm->base + REG_TCFG0); + + spin_unlock_irqrestore(&drvdata->slock, flags); +} +EXPORT_SYMBOL(samsung_pwm_set_prescale); + +void samsung_pwm_set_divisor(struct samsung_pwm *pwm, + unsigned int channel, u8 divisor) +{ + struct samsung_pwm_drvdata *drvdata = to_drvdata(pwm); + u8 shift = TCFG1_SHIFT(channel); + unsigned long flags; + u32 reg; + u8 bits; + + bits = (fls(divisor) - 1) - pwm->variant.div_base; + + spin_lock_irqsave(&drvdata->slock, flags); + + reg = readl(pwm->base + REG_TCFG1); + reg &= ~(TCFG1_MUX_MASK << shift); + reg |= bits << shift; + writel(reg, pwm->base + REG_TCFG1); + + spin_unlock_irqrestore(&drvdata->slock, flags); +} +EXPORT_SYMBOL(samsung_pwm_set_divisor); + +int samsung_pwm_request(struct samsung_pwm *pwm, unsigned int channel) +{ + struct samsung_pwm_drvdata *drvdata = to_drvdata(pwm); + + if (test_and_set_bit(channel, &drvdata->request_mask)) + return -EBUSY; + + return 0; +} +EXPORT_SYMBOL(samsung_pwm_request); + +void samsung_pwm_free(struct samsung_pwm *pwm, unsigned int channel) +{ + struct samsung_pwm_drvdata *drvdata = to_drvdata(pwm); + + clear_bit(channel, &drvdata->request_mask); +} +EXPORT_SYMBOL(samsung_pwm_free); + +#ifdef CONFIG_OF +static int samsung_pwm_parse_dt(struct samsung_pwm *pwm) +{ + struct samsung_pwm_variant *variant = &pwm->variant; + struct device_node *np = pwm->of_node; + struct property *prop; + const __be32 *cur; + u32 val; + int i; + + for (i = 0; i < SAMSUNG_PWM_NUM; ++i) + pwm->irq[i] = irq_of_parse_and_map(np, i); + + of_property_for_each_u32(np, "samsung,pwm-outputs", prop, cur, val) { + if (val >= SAMSUNG_PWM_NUM) { + pr_warning("%s: invalid channel index in samsung,pwm-outputs property\n", + __func__); + continue; + } + variant->output_mask |= 1 << val; + } + + return 0; +} + +static const struct samsung_pwm_variant s3c24xx_variant = { + .bits = 16, + .div_base = 1, + .has_tint_cstat = false, + .tclk_mask = (1 << 4), +}; + +static const struct samsung_pwm_variant s3c64xx_variant = { + .bits = 32, + .div_base = 0, + .has_tint_cstat = true, + .tclk_mask = (1 << 7) | (1 << 6) | (1 << 5), +}; + +static const struct samsung_pwm_variant s5p64x0_variant = { + .bits = 32, + .div_base = 0, + .has_tint_cstat = true, + .tclk_mask = 0, +}; + +static const struct samsung_pwm_variant s5p_variant = { + .bits = 32, + .div_base = 0, + .has_tint_cstat = true, + .tclk_mask = (1 << 5), +}; + +static const struct of_device_id samsung_pwm_matches[] = { + { .compatible = "samsung,s3c2410-pwm", .data = &s3c24xx_variant, }, + { .compatible = "samsung,s3c6400-pwm", .data = &s3c64xx_variant, }, + { .compatible = "samsung,s5p6440-pwm", .data = &s5p64x0_variant, }, + { .compatible = "samsung,s5pc100-pwm", .data = &s5p_variant, }, + {}, +}; + +static struct samsung_pwm_drvdata *samsung_pwm_of_add(struct device_node *np) +{ + const struct samsung_pwm_variant *variant; + const struct of_device_id *match; + struct samsung_pwm_drvdata *pwm; + int ret; + + if (!np) { + np = of_find_matching_node(NULL, samsung_pwm_matches); + if (!np) { + pr_err("%s: could not find PWM device\n", __func__); + return ERR_PTR(-ENODEV); + } + } + + match = of_match_node(samsung_pwm_matches, np); + if (!match) { + pr_err("%s: failed to match given OF node\n", __func__); + return ERR_PTR(-EINVAL); + } + variant = match->data; + + pwm = kzalloc(sizeof(*pwm), GFP_KERNEL); + if (!pwm) { + pr_err("%s: could not allocate PWM device struct\n", __func__); + return ERR_PTR(-ENOMEM); + } + + pwm->pwm.variant = *variant; + pwm->pwm.of_node = np; + spin_lock_init(&pwm->slock); + + ret = of_address_to_resource(np, 0, &pwm->resource); + if (ret < 0) { + pr_err("%s: could not get IO resource\n", __func__); + goto err_free; + } + + ret = samsung_pwm_parse_dt(&pwm->pwm); + if (ret < 0) { + pr_err("%s: failed to parse device tree node\n", __func__); + goto err_free; + } + + list_add_tail(&pwm->list, &pwm_list); + + return pwm; + +err_free: + kfree(pwm); + + return ERR_PTR(ret); +} +#else +static struct samsung_pwm_drvdata *samsung_pwm_of_add(struct device_node *np) +{ + return ERR_PTR(-ENODEV); +} +#endif + +static struct samsung_pwm_drvdata *samsung_pwm_add(struct platform_device *pdev) +{ + struct samsung_pwm_variant *variant = pdev->dev.platform_data; + struct samsung_pwm_drvdata *pwm; + struct resource *res; + int i; + + if (!variant) { + pr_err("%s: no platform data specified\n", __func__); + return ERR_PTR(-EINVAL); + } + + pwm = kzalloc(sizeof(*pwm), GFP_KERNEL); + if (!pwm) { + pr_err("%s: could not allocate PWM device struct\n", __func__); + return ERR_PTR(-ENOMEM); + } + + pwm->pwm.variant = *variant; + pwm->pdev = pdev; + spin_lock_init(&pwm->slock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + pr_err("%s: could not get IO resource\n", __func__); + kfree(pwm); + return ERR_PTR(-EINVAL); + } + pwm->resource = *res; + + for (i = 0; i < SAMSUNG_PWM_NUM; ++i) + pwm->pwm.irq[i] = platform_get_irq(pdev, i); + + list_add_tail(&pwm->list, &pwm_list); + + return pwm; +} + +static struct samsung_pwm_drvdata *samsung_pwm_find(struct platform_device *pdev) +{ + struct samsung_pwm_drvdata *pwm; + struct device_node *np = NULL; + + list_for_each_entry(pwm, &pwm_list, list) { + if (!pdev || pwm->pdev == pdev) + return pwm; + if (pdev->dev.of_node && pdev->dev.of_node == pwm->pwm.of_node) + return pwm; + } + + if (pdev) { + np = pdev->dev.of_node; + if (!np) + return samsung_pwm_add(pdev); + } + + return samsung_pwm_of_add(np); +} + +struct samsung_pwm *samsung_pwm_get(struct platform_device *pdev) +{ + struct samsung_pwm_drvdata *pwm; + struct resource *res; + + pwm = samsung_pwm_find(pdev); + if (IS_ERR(pwm)) { + pr_err("%s: failed to instantiate PWM device\n", __func__); + return &pwm->pwm; + } + + if (pwm->pwm.base) + return &pwm->pwm; + + res = request_mem_region(pwm->resource.start, + resource_size(&pwm->resource), "samsung-pwm"); + if (!res) { + pr_err("%s: failed to request IO mem region\n", __func__); + return ERR_PTR(-ENOMEM); + } + + pwm->pwm.base = ioremap(res->start, resource_size(res)); + if (!pwm->pwm.base) { + pr_err("%s: failed to map PWM registers\n", __func__); + release_mem_region(res->start, resource_size(res)); + return ERR_PTR(-ENOMEM); + } + + return &pwm->pwm; +} +EXPORT_SYMBOL(samsung_pwm_get); + +int samsung_pwm_register(struct platform_device *pdev) +{ + struct samsung_pwm_drvdata *pwm; + + pwm = samsung_pwm_add(pdev); + if (IS_ERR(pwm)) + return PTR_ERR(pwm); + + return 0; +} +EXPORT_SYMBOL(samsung_pwm_register); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 0e0bfa0..c80070b 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -118,6 +118,7 @@ config PWM_PXA config PWM_SAMSUNG tristate "Samsung PWM support" depends on PLAT_SAMSUNG + select MFD_SAMSUNG_PWM help Generic PWM framework driver for Samsung. diff --git a/include/linux/mfd/samsung-pwm.h b/include/linux/mfd/samsung-pwm.h new file mode 100644 index 0000000..e54456e --- /dev/null +++ b/include/linux/mfd/samsung-pwm.h @@ -0,0 +1,49 @@ +/* + * Samsung PWM/timers MFD driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * + * Author: Tomasz Figa <t.figa@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef MFD_SAMSUNG_PWM_H_ +#define MFD_SAMSUNG_PWM_H_ + +#include <linux/platform_data/samsung-pwm.h> + +#define SAMSUNG_PWM_NUM 5 + +#define SAMSUNG_PWM_INVERT (1 << 0) + +struct platform_device; + +struct samsung_pwm { + struct samsung_pwm_variant variant; + struct device_node *of_node; + void __iomem *base; + int irq[SAMSUNG_PWM_NUM]; +}; + +extern void samsung_pwm_start(struct samsung_pwm *pwm, + unsigned int channel, bool periodic); +extern void samsung_pwm_stop(struct samsung_pwm *pwm, unsigned int channel); +extern void samsung_pwm_setup(struct samsung_pwm *pwm, unsigned int channel, + u32 tcmp, u32 tcnt); + +extern void samsung_pwm_set_flags(struct samsung_pwm *pwm, + unsigned int channel, u32 flags); +extern void samsung_pwm_set_prescale(struct samsung_pwm *pwm, + unsigned int channel, u16 prescale); +extern void samsung_pwm_set_divisor(struct samsung_pwm *pwm, + unsigned int channel, u8 divisor); + +extern int samsung_pwm_request(struct samsung_pwm *pwm, unsigned int channel); +extern void samsung_pwm_free(struct samsung_pwm *pwm, unsigned int channel); + +extern struct samsung_pwm *samsung_pwm_get(struct platform_device *pdev); + +#endif diff --git a/include/linux/platform_data/samsung-pwm.h b/include/linux/platform_data/samsung-pwm.h new file mode 100644 index 0000000..8c48dbb --- /dev/null +++ b/include/linux/platform_data/samsung-pwm.h @@ -0,0 +1,28 @@ +/* + * Samsung PWM/timers driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * + * Author: Tomasz Figa <t.figa@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef PLATFORM_DATA_SAMSUNG_PWM_H_ +#define PLATFORM_DATA_SAMSUNG_PWM_H_ + +struct platform_device; + +struct samsung_pwm_variant { + u8 bits; + u8 div_base; + u8 tclk_mask; + u8 output_mask; + bool has_tint_cstat; +}; + +extern int samsung_pwm_register(struct platform_device *pdev); + +#endif