| /* | 
 |  * Blackfin Pulse Width Modulation (PWM) core | 
 |  * | 
 |  * Copyright (c) 2011 Analog Devices Inc. | 
 |  * | 
 |  * Licensed under the GPL-2 or later. | 
 |  */ | 
 |  | 
 | #include <linux/module.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/pwm.h> | 
 | #include <linux/slab.h> | 
 |  | 
 | #include <asm/gptimers.h> | 
 | #include <asm/portmux.h> | 
 |  | 
 | struct bfin_pwm_chip { | 
 | 	struct pwm_chip chip; | 
 | }; | 
 |  | 
 | struct bfin_pwm { | 
 | 	unsigned short pin; | 
 | }; | 
 |  | 
 | static const unsigned short pwm_to_gptimer_per[] = { | 
 | 	P_TMR0, P_TMR1, P_TMR2, P_TMR3, P_TMR4, P_TMR5, | 
 | 	P_TMR6, P_TMR7, P_TMR8, P_TMR9, P_TMR10, P_TMR11, | 
 | }; | 
 |  | 
 | static int bfin_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) | 
 | { | 
 | 	struct bfin_pwm *priv; | 
 | 	int ret; | 
 |  | 
 | 	if (pwm->hwpwm >= ARRAY_SIZE(pwm_to_gptimer_per)) | 
 | 		return -EINVAL; | 
 |  | 
 | 	priv = kzalloc(sizeof(*priv), GFP_KERNEL); | 
 | 	if (!priv) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	priv->pin = pwm_to_gptimer_per[pwm->hwpwm]; | 
 |  | 
 | 	ret = peripheral_request(priv->pin, NULL); | 
 | 	if (ret) { | 
 | 		kfree(priv); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	pwm_set_chip_data(pwm, priv); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void bfin_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) | 
 | { | 
 | 	struct bfin_pwm *priv = pwm_get_chip_data(pwm); | 
 |  | 
 | 	if (priv) { | 
 | 		peripheral_free(priv->pin); | 
 | 		kfree(priv); | 
 | 	} | 
 | } | 
 |  | 
 | static int bfin_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, | 
 | 		int duty_ns, int period_ns) | 
 | { | 
 | 	struct bfin_pwm *priv = pwm_get_chip_data(pwm); | 
 | 	unsigned long period, duty; | 
 | 	unsigned long long val; | 
 |  | 
 | 	val = (unsigned long long)get_sclk() * period_ns; | 
 | 	do_div(val, NSEC_PER_SEC); | 
 | 	period = val; | 
 |  | 
 | 	val = (unsigned long long)period * duty_ns; | 
 | 	do_div(val, period_ns); | 
 | 	duty = period - val; | 
 |  | 
 | 	if (duty >= period) | 
 | 		duty = period - 1; | 
 |  | 
 | 	set_gptimer_config(priv->pin, TIMER_MODE_PWM | TIMER_PERIOD_CNT); | 
 | 	set_gptimer_pwidth(priv->pin, duty); | 
 | 	set_gptimer_period(priv->pin, period); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int bfin_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) | 
 | { | 
 | 	struct bfin_pwm *priv = pwm_get_chip_data(pwm); | 
 |  | 
 | 	enable_gptimer(priv->pin); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void bfin_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) | 
 | { | 
 | 	struct bfin_pwm *priv = pwm_get_chip_data(pwm); | 
 |  | 
 | 	disable_gptimer(priv->pin); | 
 | } | 
 |  | 
 | static struct pwm_ops bfin_pwm_ops = { | 
 | 	.request = bfin_pwm_request, | 
 | 	.free = bfin_pwm_free, | 
 | 	.config = bfin_pwm_config, | 
 | 	.enable = bfin_pwm_enable, | 
 | 	.disable = bfin_pwm_disable, | 
 | 	.owner = THIS_MODULE, | 
 | }; | 
 |  | 
 | static int bfin_pwm_probe(struct platform_device *pdev) | 
 | { | 
 | 	struct bfin_pwm_chip *pwm; | 
 | 	int ret; | 
 |  | 
 | 	pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); | 
 | 	if (!pwm) { | 
 | 		dev_err(&pdev->dev, "failed to allocate memory\n"); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	platform_set_drvdata(pdev, pwm); | 
 |  | 
 | 	pwm->chip.dev = &pdev->dev; | 
 | 	pwm->chip.ops = &bfin_pwm_ops; | 
 | 	pwm->chip.base = -1; | 
 | 	pwm->chip.npwm = 12; | 
 |  | 
 | 	ret = pwmchip_add(&pwm->chip); | 
 | 	if (ret < 0) { | 
 | 		dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int bfin_pwm_remove(struct platform_device *pdev) | 
 | { | 
 | 	struct bfin_pwm_chip *pwm = platform_get_drvdata(pdev); | 
 |  | 
 | 	return pwmchip_remove(&pwm->chip); | 
 | } | 
 |  | 
 | static struct platform_driver bfin_pwm_driver = { | 
 | 	.driver = { | 
 | 		.name = "bfin-pwm", | 
 | 		.owner = THIS_MODULE, | 
 | 	}, | 
 | 	.probe = bfin_pwm_probe, | 
 | 	.remove = bfin_pwm_remove, | 
 | }; | 
 |  | 
 | module_platform_driver(bfin_pwm_driver); | 
 |  | 
 | MODULE_LICENSE("GPL"); |