/* * Hisilicon Hi3620 PWM driver * * Copyright (C) 2013 Hisilicon Ltd. * Copyright (C) 2013 Linaro Ltd. * * Author: Haojian Zhuang * * 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, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #define REG_OUT_EN 0x00 #define REG_OUT_DIV 0x08 #define REG_OUT_WIDE 0x10 #define REG_OUT_WARN 0x18 #define DELTA_NS 5 struct hi3620_pwm_info { struct pwm_chip chip; struct clk *clk; void __iomem *mmio_base; unsigned int clk_rate; int ratio; }; static inline struct hi3620_pwm_info *to_hi3620_pwm_info(struct pwm_chip *chip) { return container_of(chip, struct hi3620_pwm_info, chip); } static int hi3620_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { struct hi3620_pwm_info *info = to_hi3620_pwm_info(chip); int ratio, data; unsigned long long int c; if (!info->ratio) { c = (unsigned long long int)period_ns * info->clk_rate; do_div(c, NSEC_PER_SEC); ratio = (int)c; info->ratio = 1 << __fls(ratio); writel_relaxed(info->ratio, info->mmio_base + REG_OUT_DIV); readl_relaxed(info->mmio_base + REG_OUT_DIV); } c = (unsigned long long int)(duty_ns + DELTA_NS)* info->clk_rate; do_div(c, NSEC_PER_SEC); data = (int)c; writel_relaxed(data, info->mmio_base + REG_OUT_WIDE); readl_relaxed(info->mmio_base + REG_OUT_WIDE); return 0; } static int hi3620_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) { struct hi3620_pwm_info *info = to_hi3620_pwm_info(chip); writel_relaxed(1, info->mmio_base + REG_OUT_EN); readl_relaxed(info->mmio_base + REG_OUT_EN); return 0; } static void hi3620_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) { struct hi3620_pwm_info *info = to_hi3620_pwm_info(chip); writel_relaxed(0, info->mmio_base + REG_OUT_EN); readl_relaxed(info->mmio_base + REG_OUT_EN); } static const struct pwm_ops hi3620_pwm_ops = { .config = hi3620_pwm_config, .enable = hi3620_pwm_enable, .disable = hi3620_pwm_disable, .owner = THIS_MODULE, }; static int hi3620_pwm_probe(struct platform_device *pdev) { struct pinctrl *pinctrl; struct resource *res; struct hi3620_pwm_info *info; int ret; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "no memory resource defined\n"); return -ENODEV; } pinctrl = devm_pinctrl_get_select_default(&pdev->dev); if (IS_ERR(pinctrl)) { dev_err(&pdev->dev, "unable to select pin group\n"); return PTR_ERR(pinctrl); } info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); if (!info) { dev_err(&pdev->dev, "failed to allocate memory\n"); return -ENOMEM; } info->mmio_base = devm_request_and_ioremap(&pdev->dev, res); if (!info->mmio_base) return -EADDRNOTAVAIL; info->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(info->clk)) { dev_err(&pdev->dev, "failed to get clock\n"); return PTR_ERR(info->clk); } ret = clk_prepare_enable(info->clk); if (ret) return ret; info->clk_rate = clk_get_rate(info->clk); if (!info->clk_rate) { dev_err(&pdev->dev, "failed to get clock rate\n"); return -EINVAL; } info->chip.dev = &pdev->dev; info->chip.ops = &hi3620_pwm_ops; info->chip.base = -1; info->chip.npwm = 1; ret = pwmchip_add(&info->chip); if (ret) goto err; platform_set_drvdata(pdev, info); return 0; err: clk_disable_unprepare(info->clk); return ret; } static int hi3620_pwm_remove(struct platform_device *pdev) { struct hi3620_pwm_info *info; info = platform_get_drvdata(pdev); pwmchip_remove(&info->chip); clk_disable_unprepare(info->clk); return 0; } static struct of_device_id hi3620_pwm_of_match[] = { { .compatible = "hisilicon,hi3620-pwm" }, { } }; MODULE_DEVICE_TABLE(of, hi3620_pwm_of_match); static struct platform_driver hi3620_pwm_driver = { .driver = { .name = "hi3620-pwm", .of_match_table = of_match_ptr(hi3620_pwm_of_match), }, .probe = hi3620_pwm_probe, .remove = hi3620_pwm_remove, }; module_platform_driver(hi3620_pwm_driver); MODULE_LICENSE("GPLv2"); MODULE_AUTHOR("Haojian Zhuang