Thread (51 messages) 51 messages, 8 authors, 2013-06-24
STALE4741d

[PATCH 08/15] pwm: Add new pwm-samsung driver

From: Tomasz Figa <hidden>
Date: 2013-06-18 19:41:09
Also in: linux-pwm, linux-samsung-soc

On Monday 17 of June 2013 22:29:11 Thierry Reding wrote:
On Wed, Jun 05, 2013 at 11:18:13PM +0200, Tomasz Figa wrote:
quoted
This patch introduces new Samsung PWM driver, which uses Samsung
PWM/timer master driver to control shared parts of the hardware.

Signed-off-by: Tomasz Figa <redacted>
Sorry for jumping in so late, I've been busy with other things lately.
quoted
---

 drivers/pwm/Makefile      |   1 +
 drivers/pwm/pwm-samsung.c | 528
 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 529
 insertions(+)
 create mode 100644 drivers/pwm/pwm-samsung.c
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 229a599..833c3ac 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_PWM_MXS)		+= pwm-mxs.o

 obj-$(CONFIG_PWM_PUV3)		+= pwm-puv3.o
 obj-$(CONFIG_PWM_PXA)		+= pwm-pxa.o
 obj-$(CONFIG_PWM_SAMSUNG)	+= pwm-samsung-legacy.o

+obj-$(CONFIG_PWM_SAMSUNG)	+= pwm-samsung.o

 obj-$(CONFIG_PWM_SPEAR)		+= pwm-spear.o
 obj-$(CONFIG_PWM_TEGRA)		+= pwm-tegra.o
 obj-$(CONFIG_PWM_TIECAP)	+= pwm-tiecap.o
diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c
new file mode 100644
index 0000000..61bed3d
--- /dev/null
+++ b/drivers/pwm/pwm-samsung.c
@@ -0,0 +1,528 @@
+/* drivers/pwm/pwm-samsung.c
Nit: this line can be dropped. It serves no purpose.
quoted
+ *
+ * Copyright (c) 2007 Ben Dooks
+ * Copyright (c) 2008 Simtec Electronics
+ *     Ben Dooks [off-list ref], [off-list ref]
+ * Copyright (c) 2013 Tomasz Figa [off-list ref]
+ *
+ * PWM driver for Samsung SoCs
+ *
+ * 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. +*/
Nit: the */ should align with the * above.
quoted
+struct samsung_pwm_channel {
+	unsigned long period_ns;
+	unsigned long duty_ns;
+	unsigned long tin_ns;
+};
+
+struct samsung_pwm_chip {
+	struct pwm_chip chip;
+	struct samsung_pwm_variant variant;
+	struct samsung_pwm_channel channels[SAMSUNG_PWM_NUM];
The new driver for Renesas did something similar, but I want to
discourage storing per-channel data within the chip structure.

The PWM framework provides a way to store this information along with
the PWM device (see pwm_{set,get}_chip_data()).
OK, this looks good, but in my case is not really useful. I need to access 
those channel data in my suspend/resume callbacks and obviously I don't 
have access to any pwm_device there. Any suggestions?

Best regards,
Tomasz
quoted
+
+	void __iomem *base;
+	struct clk *base_clk;
+	struct clk *tclk0;
+	struct clk *tclk1;
+};
+#define to_samsung_pwm_chip(chip)	\
+			container_of(chip, struct samsung_pwm_chip, chip)
Can you turn this into a static inline function please?
quoted
+#ifndef CONFIG_CLKSRC_SAMSUNG_PWM
+static DEFINE_SPINLOCK(samsung_pwm_lock);
+#endif
Why is this lock global? Shouldn't it more correctly be part of
samsung_pwm_chip?
quoted
+static void pwm_samsung_set_divisor(struct samsung_pwm_chip *pwm,
+					unsigned int channel, u8 divisor)
Nit: please align arguments on subsequent lines with the first argument
of the first line. There's many more of these but I haven't mentioned
them all explicitly.
quoted
+static inline int pwm_samsung_is_tdiv(struct samsung_pwm_chip *chip,
Any particular reason for making this inline?
quoted
+static int pwm_samsung_config(struct pwm_chip *chip, struct
pwm_device *pwm, +		int duty_ns, int period_ns)
+{
+	struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip);
+	struct samsung_pwm_channel *chan = &our_chip->channels[pwm-
hwpwm];
quoted
+	unsigned long tin_ns = chan->tin_ns;
+	unsigned int tcon_chan = pwm->hwpwm;
+	unsigned long tin_rate;
+	unsigned long period;
+	unsigned long flags;
+	unsigned long tcnt;
Many of these unsigned long variable could be declared on a single line
to make the function shorter.
quoted
+	long tcmp;
+	u32 tcon;
+
+	/* We currently avoid using 64bit arithmetic by using the
+	 * fact that anything faster than 1Hz is easily representable
+	 * by 32bits. */
Can you turn these into proper block-style comments? Like so:

	/*
	 * We currently...
	 * ...
	 * by 32 bits.
	 */
quoted
+	if (period_ns > NSEC_PER_SEC || duty_ns > NSEC_PER_SEC)
+		return -ERANGE;
Note that technically you only need to check period_ns because the core
already ensures that duty_ns <= period_ns.
quoted
+static int pwm_samsung_set_polarity(struct pwm_chip *chip,
+			struct pwm_device *pwm, enum pwm_polarity 
polarity)
quoted
+{
+	struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip);
+	unsigned int channel = pwm->hwpwm;
+	unsigned long flags;
+	u32 tcon;
+
+	if (channel > 0)
+		++channel;
You have to repeat that in quite a few places, so I wonder if it'd make
sense to wrap it into a function and add a comment about why the
increment is necessary.
quoted
+static struct pwm_ops pwm_samsung_ops = {
"static const" please.
quoted
+#ifdef CONFIG_OF
+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 },
+	{ .compatible = "samsung,exynos4210-pwm", .data = &s5p64x0_variant
},
+	{},
+};
+#endif
+
+static int pwm_samsung_parse_dt(struct samsung_pwm_chip *chip)
+{
+	struct device_node *np = chip->chip.dev->of_node;
+	const struct of_device_id *match;
+	struct property *prop;
+	const __be32 *cur;
+	u32 val;
+
+	match = of_match_node(samsung_pwm_matches, np);
+	if (!match)
+		return -ENODEV;
+
+	memcpy(&chip->variant, match->data, sizeof(chip->variant));
+
+	of_property_for_each_u32(np, "samsung,pwm-outputs", prop, cur, 
val)
quoted
{
+		if (val >= SAMSUNG_PWM_NUM) {
+			pr_warning("%s: invalid channel index in 
samsung,pwm-outputs
quoted
property\n", +								
__func__);
quoted
+			continue;
+		}
+		chip->variant.output_mask |= 1 << val;
Could the output_mask be moved to the struct samsung_pwm_chip instead?
The reason I ask is because it would allow you to make the variant
constant throughout the driver.
quoted
+static int pwm_samsung_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct samsung_pwm_chip *chip;
+	struct resource *res;
+	int ret;
+
+	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL) {
+		dev_err(dev, "failed to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	chip->chip.dev = &pdev->dev;
+	chip->chip.ops = &pwm_samsung_ops;
+	chip->chip.base = -1;
+	chip->chip.npwm = SAMSUNG_PWM_NUM;
+
+	if (pdev->dev.of_node) {
Maybe add an IS_ENABLED(CONFIG_OF) check here? That'd allow all OF-
related code to be thrown away if OF isn't selected.
quoted
+		ret = pwm_samsung_parse_dt(chip);
+		if (ret)
+			return ret;
+
+		chip->chip.of_xlate = of_pwm_xlate_with_flags;
+		chip->chip.of_pwm_n_cells = 3;
+	} else {
+		if (!pdev->dev.platform_data) {
+			dev_err(&pdev->dev, "no platform data 
specified\n");
quoted
+			return -EINVAL;
+		}
+
+		memcpy(&chip->variant, pdev->dev.platform_data,
+							sizeof(chip-
variant));
quoted
+	}
Obviously this needs some modification in order for the variant to
become constant. But I think you can easily do so by making the driver
match using the platform_driver's id_table field, similar to how the
matching is done for OF.
quoted
+	chip->base = devm_request_and_ioremap(&pdev->dev, res);
+	if (!chip->base) {
+		dev_err(&pdev->dev, "failed to request and map 
registers\n");
quoted
+		return -ENOMEM;
+	}
devm_request_and_ioremap() is now deprecated and in the process of being
removed. You should use devm_ioremap_resource() instead.
quoted
+
+	chip->base_clk = devm_clk_get(&pdev->dev, "timers");
+	if (IS_ERR(chip->base_clk)) {
+		dev_err(dev, "failed to get timer base clk\n");
+		return PTR_ERR(chip->base_clk);
+	}
+	clk_prepare_enable(chip->base_clk);
You need to check the return value of clk_prepare_enable(). And if I was
very pedantic, there should be a blank line before this one.
quoted
+	ret = pwmchip_add(&chip->chip);
+	if (ret < 0) {
+		dev_err(dev, "failed to register pwm\n");
"failed to register PWM chip" please.
quoted
+		goto err_clk_disable;
+	}
+
+	dev_info(dev, "base_clk at %lu, tclk0 at %lu, tclk1 at %lu\n",
+		clk_get_rate(chip->base_clk),
+		!IS_ERR(chip->tclk0) ? clk_get_rate(chip->tclk0) : 0,
+		!IS_ERR(chip->tclk1) ? clk_get_rate(chip->tclk1) : 0);
+
+	platform_set_drvdata(pdev, chip);
+
+	return 0;
+
+err_clk_disable:
+	clk_disable_unprepare(chip->base_clk);
+
+	return ret;
There's only a single case where this can actually happen, so I don't
think you need the label here. Just put the clk_disable_unprepare() call
and the return statement where you jump to the label.
quoted
+#ifdef CONFIG_PM
I think this should really be CONFIG_PM_SLEEP.
quoted
+static struct dev_pm_ops pwm_samsung_pm_ops = {
"static const" please.

Thierry
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help