/* * 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");