| /* | 
 |  * linux/arch/unicore32/kernel/pwm.c | 
 |  * | 
 |  * Code specific to PKUnity SoC and UniCore ISA | 
 |  * | 
 |  *	Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> | 
 |  *	Copyright (C) 2001-2010 Guan Xuetao | 
 |  * | 
 |  * 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/module.h> | 
 | #include <linux/kernel.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/err.h> | 
 | #include <linux/clk.h> | 
 | #include <linux/io.h> | 
 | #include <linux/pwm.h> | 
 |  | 
 | #include <asm/div64.h> | 
 | #include <mach/hardware.h> | 
 |  | 
 | struct puv3_pwm_chip { | 
 | 	struct pwm_chip chip; | 
 | 	void __iomem *base; | 
 | 	struct clk *clk; | 
 | }; | 
 |  | 
 | static inline struct puv3_pwm_chip *to_puv3(struct pwm_chip *chip) | 
 | { | 
 | 	return container_of(chip, struct puv3_pwm_chip, chip); | 
 | } | 
 |  | 
 | /* | 
 |  * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE | 
 |  * duty_ns   = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE | 
 |  */ | 
 | static int puv3_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, | 
 | 			   int duty_ns, int period_ns) | 
 | { | 
 | 	unsigned long period_cycles, prescale, pv, dc; | 
 | 	struct puv3_pwm_chip *puv3 = to_puv3(chip); | 
 | 	unsigned long long c; | 
 |  | 
 | 	c = clk_get_rate(puv3->clk); | 
 | 	c = c * period_ns; | 
 | 	do_div(c, 1000000000); | 
 | 	period_cycles = c; | 
 |  | 
 | 	if (period_cycles < 1) | 
 | 		period_cycles = 1; | 
 |  | 
 | 	prescale = (period_cycles - 1) / 1024; | 
 | 	pv = period_cycles / (prescale + 1) - 1; | 
 |  | 
 | 	if (prescale > 63) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (duty_ns == period_ns) | 
 | 		dc = OST_PWMDCCR_FDCYCLE; | 
 | 	else | 
 | 		dc = (pv + 1) * duty_ns / period_ns; | 
 |  | 
 | 	/* | 
 | 	 * NOTE: the clock to PWM has to be enabled first | 
 | 	 * before writing to the registers | 
 | 	 */ | 
 | 	clk_prepare_enable(puv3->clk); | 
 |  | 
 | 	writel(prescale, puv3->base + OST_PWM_PWCR); | 
 | 	writel(pv - dc, puv3->base + OST_PWM_DCCR); | 
 | 	writel(pv, puv3->base + OST_PWM_PCR); | 
 |  | 
 | 	clk_disable_unprepare(puv3->clk); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int puv3_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) | 
 | { | 
 | 	struct puv3_pwm_chip *puv3 = to_puv3(chip); | 
 |  | 
 | 	return clk_prepare_enable(puv3->clk); | 
 | } | 
 |  | 
 | static void puv3_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) | 
 | { | 
 | 	struct puv3_pwm_chip *puv3 = to_puv3(chip); | 
 |  | 
 | 	clk_disable_unprepare(puv3->clk); | 
 | } | 
 |  | 
 | static const struct pwm_ops puv3_pwm_ops = { | 
 | 	.config = puv3_pwm_config, | 
 | 	.enable = puv3_pwm_enable, | 
 | 	.disable = puv3_pwm_disable, | 
 | 	.owner = THIS_MODULE, | 
 | }; | 
 |  | 
 | static int pwm_probe(struct platform_device *pdev) | 
 | { | 
 | 	struct puv3_pwm_chip *puv3; | 
 | 	struct resource *r; | 
 | 	int ret; | 
 |  | 
 | 	puv3 = devm_kzalloc(&pdev->dev, sizeof(*puv3), GFP_KERNEL); | 
 | 	if (!puv3) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	puv3->clk = devm_clk_get(&pdev->dev, "OST_CLK"); | 
 | 	if (IS_ERR(puv3->clk)) | 
 | 		return PTR_ERR(puv3->clk); | 
 |  | 
 | 	r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
 | 	puv3->base = devm_ioremap_resource(&pdev->dev, r); | 
 | 	if (IS_ERR(puv3->base)) | 
 | 		return PTR_ERR(puv3->base); | 
 |  | 
 | 	puv3->chip.dev = &pdev->dev; | 
 | 	puv3->chip.ops = &puv3_pwm_ops; | 
 | 	puv3->chip.base = -1; | 
 | 	puv3->chip.npwm = 1; | 
 |  | 
 | 	ret = pwmchip_add(&puv3->chip); | 
 | 	if (ret < 0) { | 
 | 		dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	platform_set_drvdata(pdev, puv3); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int pwm_remove(struct platform_device *pdev) | 
 | { | 
 | 	struct puv3_pwm_chip *puv3 = platform_get_drvdata(pdev); | 
 |  | 
 | 	return pwmchip_remove(&puv3->chip); | 
 | } | 
 |  | 
 | static struct platform_driver puv3_pwm_driver = { | 
 | 	.driver = { | 
 | 		.name = "PKUnity-v3-PWM", | 
 | 	}, | 
 | 	.probe = pwm_probe, | 
 | 	.remove = pwm_remove, | 
 | }; | 
 | module_platform_driver(puv3_pwm_driver); | 
 |  | 
 | MODULE_LICENSE("GPL v2"); |