[PATCH 17/30] pwm: samsung: repair the worst MMIO abuses

Arnd Bergmann arnd at arndb.de
Wed Apr 10 20:04:59 EDT 2013


The Samsung PWM driver uses "magic" pointers that are mapped
at boot time to point its MMIO registers. This fails horribly
with a multiplatform kernel, which can not rely on platform
specific header files to contain the right values, aside from
this being a really bad idea in general.

This changes the driver to at least pass an __iomem token
around in the device structure to dereference that. Fixing
the platform code is much harder, so we'll leave that
until we have a DT binding for pwm-samsung, which may require
other changes in this area. Since we are already touching
every MMIO accessor in this driver, let's also use the
proper readl_relaxed variant rather than __raw_readl.

Tomasz Figa has a set of patches to clean this up in a proper
way, but that might be too late for 3.10.

Signed-off-by: Arnd Bergmann <arnd at arndb.de>
Cc: Tomasz Figa <t.figa at samsung.com>
Cc: Thierry Reding <thierry.reding at avionic-design.de>
---
 drivers/pwm/pwm-samsung.c | 60 +++++++++++++++++++++++++++++++++--------------
 1 file changed, 42 insertions(+), 18 deletions(-)

diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c
index 5207e6c..9d7234d 100644
--- a/drivers/pwm/pwm-samsung.c
+++ b/drivers/pwm/pwm-samsung.c
@@ -22,9 +22,25 @@
 #include <linux/io.h>
 #include <linux/pwm.h>
 
-#include <mach/map.h>
+#ifndef CONFIG_ARCH_MULTIPLATFORM
+/*
+ * This is gross: the platform maps the timer at a fixed
+ * virtual address and expects us to use that address
+ * rather than a proper resource. This should get done properly
+ * when we get a DT binding for this driver.
+ */
+#include <plat/map-base.h>
+static inline void dummy(void)
+{
+	BUILD_BUG_ON(S3C_VA_TIMER != IOMEM(0xf6300000));
+}
+#else
+#define S3C_VA_TIMER IOMEM(0xf6300000);
+#endif
 
-#include <plat/regs-timer.h>
+#define S3C2410_TCON		8
+#define S3C2410_TCNTB(tmr)	(0x0c + (tmr)*0x0c + 0x00)
+#define S3C2410_TCMPB(tmr)	(0x0c + (tmr)*0x0c + 0x04)
 
 struct s3c_chip {
 	struct platform_device	*pdev;
@@ -38,6 +54,7 @@ struct s3c_chip {
 
 	unsigned char		 tcon_base;
 	unsigned char		 pwm_id;
+	void __iomem		*base;
 	struct pwm_chip		 chip;
 };
 
@@ -65,9 +82,9 @@ static int s3c_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
 
 	local_irq_save(flags);
 
-	tcon = __raw_readl(S3C2410_TCON);
+	tcon = readl_relaxed(s3c->base + S3C2410_TCON);
 	tcon |= pwm_tcon_start(s3c);
-	__raw_writel(tcon, S3C2410_TCON);
+	writel_relaxed(tcon, s3c->base + S3C2410_TCON);
 
 	local_irq_restore(flags);
 
@@ -82,9 +99,9 @@ static void s3c_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
 
 	local_irq_save(flags);
 
-	tcon = __raw_readl(S3C2410_TCON);
+	tcon = readl_relaxed(s3c->base + S3C2410_TCON);
 	tcon &= ~pwm_tcon_start(s3c);
-	__raw_writel(tcon, S3C2410_TCON);
+	writel_relaxed(tcon, s3c->base + S3C2410_TCON);
 
 	local_irq_restore(flags);
 }
@@ -133,8 +150,8 @@ static int s3c_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
 	/* The TCMP and TCNT can be read without a lock, they're not
 	 * shared between the timers. */
 
-	tcmp = __raw_readl(S3C2410_TCMPB(s3c->pwm_id));
-	tcnt = __raw_readl(S3C2410_TCNTB(s3c->pwm_id));
+	tcmp = readl_relaxed(s3c->base + S3C2410_TCMPB(s3c->pwm_id));
+	tcnt = readl_relaxed(s3c->base + S3C2410_TCNTB(s3c->pwm_id));
 
 	period = NS_IN_HZ / period_ns;
 
@@ -177,16 +194,16 @@ static int s3c_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
 
 	local_irq_save(flags);
 
-	__raw_writel(tcmp, S3C2410_TCMPB(s3c->pwm_id));
-	__raw_writel(tcnt, S3C2410_TCNTB(s3c->pwm_id));
+	writel_relaxed(tcmp, s3c->base + S3C2410_TCMPB(s3c->pwm_id));
+	writel_relaxed(tcnt, s3c->base + S3C2410_TCNTB(s3c->pwm_id));
 
-	tcon = __raw_readl(S3C2410_TCON);
+	tcon = readl_relaxed(s3c->base + S3C2410_TCON);
 	tcon |= pwm_tcon_manulupdate(s3c);
 	tcon |= pwm_tcon_autoreload(s3c);
-	__raw_writel(tcon, S3C2410_TCON);
+	writel_relaxed(tcon, s3c->base + S3C2410_TCON);
 
 	tcon &= ~pwm_tcon_manulupdate(s3c);
-	__raw_writel(tcon, S3C2410_TCON);
+	writel_relaxed(tcon, s3c->base + S3C2410_TCON);
 
 	local_irq_restore(flags);
 
@@ -207,6 +224,7 @@ static int s3c_pwm_probe(struct platform_device *pdev)
 	unsigned long flags;
 	unsigned long tcon;
 	unsigned int id = pdev->id;
+	struct resource *res;
 	int ret;
 
 	if (id == 4) {
@@ -220,6 +238,12 @@ static int s3c_pwm_probe(struct platform_device *pdev)
 		return -ENOMEM;
 	}
 
+	/* try to get a proper base address, fall back to S3C_VA_TIMER */
+	s3c->base = S3C_VA_TIMER;
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res)
+		s3c->base = devm_ioremap(dev, res->start, resource_size(res));
+
 	/* calculate base of control bits in TCON */
 	s3c->tcon_base = id == 0 ? 0 : (id * 4) + 4;
 	s3c->pwm_id = id;
@@ -245,9 +269,9 @@ static int s3c_pwm_probe(struct platform_device *pdev)
 
 	local_irq_save(flags);
 
-	tcon = __raw_readl(S3C2410_TCON);
+	tcon = readl_relaxed(s3c->base + S3C2410_TCON);
 	tcon |= pwm_tcon_invert(s3c);
-	__raw_writel(tcon, S3C2410_TCON);
+	writel_relaxed(tcon, s3c->base + S3C2410_TCON);
 
 	local_irq_restore(flags);
 
@@ -258,7 +282,7 @@ static int s3c_pwm_probe(struct platform_device *pdev)
 	}
 
 	pwm_dbg(s3c, "config bits %02x\n",
-		(__raw_readl(S3C2410_TCON) >> s3c->tcon_base) & 0x0f);
+		(readl_relaxed(s3c->base + S3C2410_TCON) >> s3c->tcon_base) & 0x0f);
 
 	dev_info(dev, "tin at %lu, tdiv at %lu, tin=%sclk, base %d\n",
 		 clk_get_rate(s3c->clk),
@@ -310,9 +334,9 @@ static int s3c_pwm_resume(struct platform_device *pdev)
 	unsigned long tcon;
 
 	/* Restore invertion */
-	tcon = __raw_readl(S3C2410_TCON);
+	tcon = readl_relaxed(s3c->base + S3C2410_TCON);
 	tcon |= pwm_tcon_invert(s3c);
-	__raw_writel(tcon, S3C2410_TCON);
+	writel_relaxed(tcon, s3c->base + S3C2410_TCON);
 
 	return 0;
 }
-- 
1.8.1.2




More information about the linux-arm-kernel mailing list