[linux-yocto] [PATCH 05/65] pwm: add owl pwm driver

Jiang Lu lu.jiang at windriver.com
Wed Dec 21 01:16:06 PST 2016


From: wurui <wurui at actions-semi.com>

commit 83f6a1ededf82a042d8b4b6b0bdd864b12497031 from
https://github.com/xapp-le/kernel.git

Change-Id: I429248b4efa7ab074a1149c152bd699f9066d281
---
 drivers/pwm/Kconfig   |   8 +
 drivers/pwm/Makefile  |   1 +
 drivers/pwm/pwm-owl.c | 523 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 532 insertions(+)
 mode change 100644 => 100755 drivers/pwm/Kconfig
 mode change 100644 => 100755 drivers/pwm/Makefile
 create mode 100755 drivers/pwm/pwm-owl.c

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
old mode 100644
new mode 100755
index 91a08a5..87afc7a
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -368,4 +368,12 @@ config PWM_VT8500
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-vt8500.
 
+config PWM_OWL
+	tristate "OWL PWM support"
+	depends on ARCH_OWL
+	help
+		ACTIONS OWL PWM device support for generic pwm framework
+
+		To compile this driver as a module, choose M here: the module
+		will be called pwm-owl
 endif
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
old mode 100644
new mode 100755
index ec50eb5..8d85618
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -35,3 +35,4 @@ obj-$(CONFIG_PWM_TIPWMSS)	+= pwm-tipwmss.o
 obj-$(CONFIG_PWM_TWL)		+= pwm-twl.o
 obj-$(CONFIG_PWM_TWL_LED)	+= pwm-twl-led.o
 obj-$(CONFIG_PWM_VT8500)	+= pwm-vt8500.o
+obj-$(CONFIG_PWM_OWL)	+= pwm-owl.o
diff --git a/drivers/pwm/pwm-owl.c b/drivers/pwm/pwm-owl.c
new file mode 100755
index 0000000..eb272f0
--- /dev/null
+++ b/drivers/pwm/pwm-owl.c
@@ -0,0 +1,523 @@
+/* arch/arm/plat-s3c24xx/pwm.c
+ *
+ * Copyright (c) 2007 Ben Dooks
+ * Copyright (c) 2008 Simtec Electronics
+ *	Ben Dooks <ben at simtec.co.uk>, <ben-linux at fluff.org>
+ *
+ * S3C24XX PWM device core
+ *
+ * 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.
+*/
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/gpio.h>
+#include <linux/io.h>
+#include <linux/pwm.h>
+#include <linux/slab.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/of_device.h>
+
+#include <mach/hardware.h>
+#include <mach/module-owl.h>
+#include <mach/clkname.h>
+#include <mach/pwm-owl.h>
+
+#define MAX_NUM_PWM 6
+
+struct pwm_chip_data {
+	int chip_type;	//0-atm5206tc&atm5203; 1-atm5206
+	int pwm_num;	//atm5206tc&atm5203 == 4; 1-atm5206 == 6
+};
+
+struct gl520x_pwm_dev {
+	struct clk *clk;
+	u32 module_id;
+
+	unsigned int required_period_ns;
+	unsigned int period_ns;
+	unsigned int duty_ns;
+	unsigned int counter_steps;
+
+	struct device dev;
+	struct pinctrl *pinctrl;
+};
+
+struct gl520x_pwm_chip {
+	struct platform_device *pdev;
+
+	struct pwm_chip chip;
+	const struct pwm_chip_data *chip_data;
+	struct gl520x_pwm_dev pwm_dev[MAX_NUM_PWM];
+};
+
+static const struct pwm_chip_data *g_chip_data;
+
+unsigned int pwm_ctl_reg_array[] = {
+	PWM_CTL0, PWM_CTL1, PWM_CTL2, PWM_CTL3, PWM_CTL4, PWM_CTL5
+};
+
+#if 0
+#define PWM_PRINT(fmt, args...) printk(KERN_INFO fmt, ##args)
+#else
+#define PWM_PRINT(fmt, args...)
+#endif
+
+#define NS_IN_HZ (1000000000UL)
+
+/********************************/
+static inline struct gl520x_pwm_chip *to_gl520x_pwm_chip(struct pwm_chip *chip)
+{
+	return container_of(chip, struct gl520x_pwm_chip, chip);
+}
+
+static inline int get_pwm_clk_info(struct pwm_chip *chip,
+	int hwpwm, struct clk **clk, u32 *module_id)
+{
+	const char *clk_name;
+
+	switch (hwpwm) {
+	case 0:
+		clk_name = CLKNAME_PWM0_CLK;
+		*module_id = MODULE_CLK_PWM0;
+		break;
+
+	case 1:
+		clk_name = CLKNAME_PWM1_CLK;
+		*module_id = MODULE_CLK_PWM1;
+		break;
+
+	case 2:
+		clk_name = CLKNAME_PWM2_CLK;
+		*module_id = MODULE_CLK_PWM2;
+		break;
+
+	case 3:
+		clk_name = CLKNAME_PWM3_CLK;
+		*module_id = MODULE_CLK_PWM3;
+		break;
+
+	case 4:
+		clk_name = CLKNAME_PWM4_CLK;
+		*module_id = MODULE_CLK_PWM4;
+		break;
+
+	case 5:
+		clk_name = CLKNAME_PWM5_CLK;
+		*module_id = MODULE_CLK_PWM5;
+		break;
+
+	default:
+			printk(KERN_ALERT "bad pwm id %d\n", hwpwm);
+			return -EINVAL;
+	}
+
+	*clk = clk_get(chip->dev, clk_name);
+	return 0;
+}
+
+static int gl520x_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+			    int duty_ns, int period_ns)
+{
+	unsigned int tmp = 0, val, valc;
+	s32 rate = 0;
+	struct clk *parent_clk;
+	struct gl520x_pwm_chip *pc = to_gl520x_pwm_chip(chip);
+	struct gl520x_pwm_dev *pwm_dev;
+	int hwpwm;
+	uint counter_steps, comparator_steps, real_period_ns;
+	u32 pwm_ctl_reg;
+
+	/* We currently avoid using 64bit arithmetic by using the
+	 * fact that anything faster than 1Hz is easily representable
+	 * by 32bits. */
+
+	if (period_ns > NS_IN_HZ || duty_ns > NS_IN_HZ)
+		return -ERANGE;
+
+	hwpwm = pwm->hwpwm;
+
+	pwm_dev = &pc->pwm_dev[hwpwm];
+
+	if (period_ns != pwm_dev->required_period_ns) {
+		uint parent_clk_freq, whole_div, pre_scale;
+		uint rq_pwm_src_clk, real_pwm_src_clk;
+
+		pwm_dev->required_period_ns = period_ns;
+
+		rate = NS_IN_HZ / period_ns; /* PWM output freq in Hz */
+		if (rate < 512) {
+			parent_clk = clk_get(NULL, CLKNAME_IC_32K);
+		} else if (rate <= 375000) {
+			parent_clk = clk_get(NULL, CLKNAME_HOSC);
+		} else {
+			rate = 375000;
+			parent_clk = clk_get(NULL, CLKNAME_HOSC);
+			PWM_PRINT("pwm freq will be 375kHZ at most!\n");
+		}
+		parent_clk_freq = clk_get_rate(parent_clk);
+
+		if(pc->chip_data->chip_type == 1) {
+			/* 5206 */
+			whole_div = (parent_clk_freq + rate / 2U) / rate;
+			pre_scale = (whole_div + 1023U) / 1024U;
+			counter_steps = (whole_div + pre_scale - 1) / pre_scale;
+			if (counter_steps > 1024U)
+				counter_steps = 1024U;
+			rq_pwm_src_clk = parent_clk_freq / pre_scale;
+		} else {
+			/* 5203 */
+			rq_pwm_src_clk = rate * 64U;
+			counter_steps = 64U;
+		}
+		pwm_dev->counter_steps = counter_steps;
+
+		/* setup clock source */
+		clk_set_parent(pwm_dev->clk, parent_clk);
+		clk_set_rate(pwm_dev->clk, rq_pwm_src_clk);
+		real_pwm_src_clk = clk_get_rate(pwm_dev->clk);
+
+		real_period_ns = div_u64((u64)counter_steps * NS_IN_HZ, real_pwm_src_clk);
+		pwm_dev->period_ns = real_period_ns;
+
+		dev_dbg(&(pwm_dev->dev), "set period, rq_period_ns=%d parent_clk=%u "
+			"rq_pwm_src_clk=%u real_pwm_src_clk=%u counter_steps=%u "
+			"real_period_ns=%u\n",
+			period_ns, parent_clk_freq, rq_pwm_src_clk, real_pwm_src_clk,
+			counter_steps, real_period_ns);
+	} else {
+		counter_steps = pwm_dev->counter_steps;
+	}
+
+	comparator_steps = div_u64((u64)duty_ns * counter_steps + period_ns / 2U, period_ns);
+	if (comparator_steps > counter_steps)
+		comparator_steps = counter_steps;
+	pwm_dev->duty_ns = div_u64(pwm_dev->period_ns * (u64)comparator_steps, counter_steps);
+
+	dev_dbg(&(pwm_dev->dev), "set duty, rq_duty_ns=%d rq_period_ns=%d "
+		"counter_steps=%u comparator_steps=%u\n",
+		duty_ns, period_ns, counter_steps, comparator_steps);
+
+	pwm_ctl_reg = pwm_ctl_reg_array[hwpwm];
+	tmp = act_readl(pwm_ctl_reg);
+
+	if(pc->chip_data->chip_type == 1) {
+		val = (comparator_steps != 0) ? (comparator_steps - 1) : 0; /* to reg value */
+		valc = (counter_steps != 0) ? (counter_steps - 1) : 0;
+		tmp &= ~((1U << 20) -1U);
+		tmp |= (val << 10) | valc;
+	} else {
+		val = (comparator_steps != 0) ? comparator_steps - 1 : 0; /* to reg value */
+		tmp &= (~0x3f);
+		tmp |= (val & 0x3f);
+	}
+	act_writel(tmp, pwm_ctl_reg);
+
+	return 0;
+}
+
+int owl_pwm_get_duty_cfg(struct pwm_device *pwm, uint *comparator_steps, uint *counter_steps)
+{
+	uint hwpwm;
+	u32 pwm_ctl_reg, reg_val;
+
+	if (pwm == NULL || comparator_steps == NULL || counter_steps == NULL)
+		return -EINVAL;
+
+	hwpwm = pwm->hwpwm;
+	pwm_ctl_reg = pwm_ctl_reg_array[hwpwm];
+
+	reg_val = act_readl(pwm_ctl_reg);
+	if(g_chip_data->chip_type == 1) {
+		*comparator_steps = ((reg_val >> 10) & ((1U << 10) -1U)) + 1U;
+		*counter_steps = (reg_val & ((1U << 10) -1U)) + 1U;
+	} else {
+		*comparator_steps = (reg_val & 0x3f) + 1U;
+		*counter_steps = 64;
+	}
+	return 0;
+}
+EXPORT_SYMBOL(owl_pwm_get_duty_cfg);
+
+enum pwm_polarity owl_pwm_get_polarity(struct pwm_device *pwm)
+{
+	uint hwpwm;
+	u32 pwm_ctl_reg;
+	u32 val;
+
+	PWM_PRINT("%s\n", __func__);
+
+	hwpwm = pwm->hwpwm;
+
+	pwm_ctl_reg = pwm_ctl_reg_array[hwpwm];
+
+	val = act_readl(pwm_ctl_reg);
+	if(g_chip_data->chip_type == 1) {
+		if (val & (1U << 20))
+			return PWM_POLARITY_NORMAL;
+		else
+			return PWM_POLARITY_INVERSED;
+	} else {
+		if (val & (0x1 << 8))
+			return PWM_POLARITY_NORMAL;
+		else
+			return PWM_POLARITY_INVERSED;
+	}
+}
+EXPORT_SYMBOL(owl_pwm_get_polarity);
+
+static int gl520x_pwm_set_polarity(
+		struct pwm_chip *chip, struct pwm_device *pwm,
+		enum pwm_polarity polarity)
+{
+	int hwpwm;
+	u32 pwm_ctl_reg;
+	struct gl520x_pwm_chip *pc = to_gl520x_pwm_chip(chip);
+
+	PWM_PRINT("%s\n", __func__);
+
+	hwpwm = pwm->hwpwm;
+
+	pwm_ctl_reg = pwm_ctl_reg_array[hwpwm];
+
+	if(pc->chip_data->chip_type == 1) {
+		if (polarity == PWM_POLARITY_INVERSED) {
+			PWM_PRINT("pwm inverse\n");
+			act_clearl(1U << 20, pwm_ctl_reg);
+		} else {
+			PWM_PRINT("pwm not inverse\n");
+			/* Duty cycle defines HIGH period of PWM */
+			act_setl(1U << 20, pwm_ctl_reg);
+		}
+	} else {
+		if (polarity == PWM_POLARITY_INVERSED) {
+			PWM_PRINT("pwm inverse\n");
+			act_clearl(0x1 << 8, pwm_ctl_reg);
+		} else {
+			PWM_PRINT("pwm not inverse\n");
+			/* Duty cycle defines HIGH period of PWM */
+			act_setl(0x1 << 8, pwm_ctl_reg);
+		}
+	}
+	PWM_PRINT("%s END\n", __func__);
+
+	return 0;
+}
+
+static int gl520x_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct gl520x_pwm_chip *pc = to_gl520x_pwm_chip(chip);
+	int ret = 0;
+	int hwpwm;
+
+	PWM_PRINT("%s\n", __func__);
+
+	hwpwm = pwm->hwpwm;
+
+	module_clk_enable(pc->pwm_dev[hwpwm].module_id);
+	ret = clk_prepare_enable(pc->pwm_dev[hwpwm].clk);
+	if (ret < 0)
+		return ret;
+
+	PWM_PRINT("%s END\n", __func__);
+
+	return 0;
+}
+
+static void gl520x_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct gl520x_pwm_chip *pc = to_gl520x_pwm_chip(chip);
+	int hwpwm;
+
+	PWM_PRINT("%s\n", __func__);
+
+	hwpwm = pwm->hwpwm;
+
+	clk_disable_unprepare(pc->pwm_dev[hwpwm].clk);
+	module_clk_disable(pc->pwm_dev[hwpwm].module_id);
+
+	PWM_PRINT("%s END\n", __func__);
+
+	return ;
+}
+
+static int gl520x_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct gl520x_pwm_chip *pc = to_gl520x_pwm_chip(chip);
+	int ret = 0;
+	int hwpwm;
+	struct gl520x_pwm_dev *pwm_dev;
+
+	PWM_PRINT("%s\n", __func__);
+
+	hwpwm = pwm->hwpwm;
+	pwm_dev = &pc->pwm_dev[hwpwm];
+
+	PWM_PRINT("%s REQ ID = %d\n", __func__, hwpwm);
+
+	ret = get_pwm_clk_info(chip, hwpwm, &pwm_dev->clk, &pwm_dev->module_id);
+	if (ret)
+		return ret;
+
+	PWM_PRINT("%s 2\n", __func__);
+
+	pwm_dev->pinctrl = pinctrl_get_select_default(&pwm_dev->dev);
+	if (IS_ERR(pwm_dev->pinctrl))
+		return PTR_ERR(pwm_dev->pinctrl);
+
+	PWM_PRINT("%s END\n", __func__);
+
+	return 0;
+}
+
+static void gl520x_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct gl520x_pwm_chip *pc = to_gl520x_pwm_chip(chip);
+	int hwpwm;
+	struct gl520x_pwm_dev *pwm_dev;
+
+	PWM_PRINT("%s\n", __func__);
+
+	hwpwm = pwm->hwpwm;
+	pwm_dev = &pc->pwm_dev[hwpwm];
+
+	if (test_bit(PWMF_ENABLED, &pwm->flags))
+		gl520x_pwm_disable(chip, pwm);
+
+	clk_put(pwm_dev->clk);
+
+	pinctrl_put(pwm_dev->pinctrl);
+
+	PWM_PRINT("%s END\n", __func__);
+
+	return ;
+
+}
+
+static const struct pwm_ops gl520x_pwm_ops = {
+	.request = gl520x_pwm_request,
+	.free = gl520x_pwm_free,
+	.config = gl520x_pwm_config,
+	.set_polarity = gl520x_pwm_set_polarity,
+	.enable = gl520x_pwm_enable,
+	.disable = gl520x_pwm_disable,
+	.owner = THIS_MODULE,
+};
+
+static int gl520x_of_pwm_devs_init(struct platform_device *pdev)
+{
+	struct device_node *child;
+	int hwpwm;
+	struct gl520x_pwm_chip *pc = platform_get_drvdata(pdev);
+	struct gl520x_pwm_dev *pwm_dev;
+
+	PWM_PRINT("%s\n", __func__);
+
+	for_each_child_of_node(pdev->dev.of_node, child) {
+		if (of_property_read_u32(child, "id", &hwpwm))
+			return -EINVAL;
+
+		PWM_PRINT("%s, child id = %d\n", __func__, hwpwm);
+
+		if (hwpwm >= pc->chip_data->pwm_num)
+			return -EINVAL;
+
+		pwm_dev = &pc->pwm_dev[hwpwm];
+		device_initialize(&pwm_dev->dev);
+		dev_set_name(&pwm_dev->dev, "gl5206-pwm%d", hwpwm);
+		pwm_dev->dev.of_node = child;
+	}
+	PWM_PRINT("%s OK\n", __func__);
+	return 0;
+}
+
+static struct pwm_chip_data pwm_chip_data[] = {
+	{ .chip_type = 0, .pwm_num = 4 }, //atm5206tc&atm5203
+	{ .chip_type = 1, .pwm_num = 6 }, //atm5206
+};
+
+static struct of_device_id gl520x_pwm_of_match[] = {
+	{ .compatible = "actions,atm7039c-pwm", .data = &pwm_chip_data[0] },
+	{ .compatible = "actions,atm7059a-pwm", .data = &pwm_chip_data[1] },
+	{ }
+};
+
+static int gl520x_pwm_probe(struct platform_device *pdev)
+{
+	const struct of_device_id *of_id;
+	struct device *dev = &pdev->dev;
+	struct gl520x_pwm_chip *pwm;
+	int ret;
+
+	dev_info(&pdev->dev, "Probing...\n");
+
+	pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
+	if (!pwm) {
+		dev_err(dev, "failed to allocate pwm_device\n");
+		return -ENOMEM;
+	}
+
+	pwm->pdev = pdev;
+
+	of_id = of_match_device(gl520x_pwm_of_match, &pdev->dev);
+	if (of_id)
+		pwm->chip_data = of_id->data;
+	else
+		return -EINVAL;
+	
+	g_chip_data = pwm->chip_data;
+	
+	platform_set_drvdata(pdev, pwm);
+
+	pwm->chip.dev = &pdev->dev;
+	pwm->chip.ops = &gl520x_pwm_ops;
+	pwm->chip.of_xlate = of_pwm_xlate_with_flags;
+	pwm->chip.of_pwm_n_cells = 3;
+	pwm->chip.base = -1;
+	pwm->chip.npwm = pwm->chip_data->pwm_num;
+
+	if (gl520x_of_pwm_devs_init(pdev))
+		return -EINVAL;
+
+	ret = pwmchip_add(&pwm->chip);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int gl520x_pwm_remove(struct platform_device *pdev)
+{
+	struct gl520x_pwm_chip *pc = platform_get_drvdata(pdev);
+
+	if (WARN_ON(!pc))
+		return -ENODEV;
+
+	return pwmchip_remove(&pc->chip);
+}
+
+static struct platform_driver gl5201_pwm_driver = {
+	.driver = {
+		.name = "pwm-owl",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(gl520x_pwm_of_match),
+	},
+	.probe = gl520x_pwm_probe,
+	.remove = gl520x_pwm_remove,
+};
+
+static int __init pwm_init(void)
+{
+	PWM_PRINT("platform register pwm driver: %s\n",
+		gl5201_pwm_driver.driver.name);
+	return platform_driver_register(&gl5201_pwm_driver);
+}
+
+subsys_initcall(pwm_init);
-- 
2.7.4



More information about the linux-yocto mailing list