17eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan/* 27eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan * Cirrus Logic CLPS711X PWM driver 37eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan * 47eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan * Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru> 57eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan * 67eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan * This program is free software; you can redistribute it and/or modify 77eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan * it under the terms of the GNU General Public License as published by 87eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan * the Free Software Foundation; either version 2 of the License, or 97eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan * (at your option) any later version. 107eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan */ 117eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 127eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan#include <linux/clk.h> 137eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan#include <linux/io.h> 147eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan#include <linux/module.h> 157eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan#include <linux/of.h> 167eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan#include <linux/platform_device.h> 177eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan#include <linux/pwm.h> 187eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 197eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyanstruct clps711x_chip { 207eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan struct pwm_chip chip; 217eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan void __iomem *pmpcon; 227eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan struct clk *clk; 237eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan spinlock_t lock; 247eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan}; 257eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 267eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyanstatic inline struct clps711x_chip *to_clps711x_chip(struct pwm_chip *chip) 277eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan{ 287eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan return container_of(chip, struct clps711x_chip, chip); 297eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan} 307eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 317eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyanstatic void clps711x_pwm_update_val(struct clps711x_chip *priv, u32 n, u32 v) 327eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan{ 337eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan /* PWM0 - bits 4..7, PWM1 - bits 8..11 */ 347eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan u32 shift = (n + 1) * 4; 357eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan unsigned long flags; 367eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan u32 tmp; 377eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 387eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan spin_lock_irqsave(&priv->lock, flags); 397eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 407eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan tmp = readl(priv->pmpcon); 417eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan tmp &= ~(0xf << shift); 427eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan tmp |= v << shift; 437eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan writel(tmp, priv->pmpcon); 447eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 457eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan spin_unlock_irqrestore(&priv->lock, flags); 467eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan} 477eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 487eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyanstatic unsigned int clps711x_get_duty(struct pwm_device *pwm, unsigned int v) 497eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan{ 507eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan /* Duty cycle 0..15 max */ 517eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan return DIV_ROUND_CLOSEST(v * 0xf, pwm_get_period(pwm)); 527eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan} 537eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 547eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyanstatic int clps711x_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) 557eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan{ 567eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan struct clps711x_chip *priv = to_clps711x_chip(chip); 577eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan unsigned int freq = clk_get_rate(priv->clk); 587eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 597eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan if (!freq) 607eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan return -EINVAL; 617eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 627eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan /* Store constant period value */ 637eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan pwm_set_period(pwm, DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq)); 647eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 657eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan return 0; 667eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan} 677eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 687eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyanstatic int clps711x_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, 697eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan int duty_ns, int period_ns) 707eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan{ 717eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan struct clps711x_chip *priv = to_clps711x_chip(chip); 727eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan unsigned int duty; 737eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 747eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan if (period_ns != pwm_get_period(pwm)) 757eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan return -EINVAL; 767eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 777eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan duty = clps711x_get_duty(pwm, duty_ns); 787eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan clps711x_pwm_update_val(priv, pwm->hwpwm, duty); 797eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 807eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan return 0; 817eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan} 827eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 837eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyanstatic int clps711x_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) 847eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan{ 857eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan struct clps711x_chip *priv = to_clps711x_chip(chip); 867eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan unsigned int duty; 877eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 887eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan duty = clps711x_get_duty(pwm, pwm_get_duty_cycle(pwm)); 897eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan clps711x_pwm_update_val(priv, pwm->hwpwm, duty); 907eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 917eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan return 0; 927eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan} 937eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 947eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyanstatic void clps711x_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) 957eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan{ 967eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan struct clps711x_chip *priv = to_clps711x_chip(chip); 977eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 987eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan clps711x_pwm_update_val(priv, pwm->hwpwm, 0); 997eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan} 1007eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 1017eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyanstatic const struct pwm_ops clps711x_pwm_ops = { 1027eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan .request = clps711x_pwm_request, 1037eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan .config = clps711x_pwm_config, 1047eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan .enable = clps711x_pwm_enable, 1057eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan .disable = clps711x_pwm_disable, 1067eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan .owner = THIS_MODULE, 1077eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan}; 1087eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 1097eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyanstatic struct pwm_device *clps711x_pwm_xlate(struct pwm_chip *chip, 1107eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan const struct of_phandle_args *args) 1117eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan{ 1127eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan if (args->args[0] >= chip->npwm) 1137eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan return ERR_PTR(-EINVAL); 1147eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 1157eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan return pwm_request_from_chip(chip, args->args[0], NULL); 1167eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan} 1177eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 1187eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyanstatic int clps711x_pwm_probe(struct platform_device *pdev) 1197eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan{ 1207eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan struct clps711x_chip *priv; 1217eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan struct resource *res; 1227eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 1237eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 1247eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan if (!priv) 1257eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan return -ENOMEM; 1267eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 1277eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1287eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan priv->pmpcon = devm_ioremap_resource(&pdev->dev, res); 1297eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan if (IS_ERR(priv->pmpcon)) 1307eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan return PTR_ERR(priv->pmpcon); 1317eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 1327eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan priv->clk = devm_clk_get(&pdev->dev, NULL); 1337eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan if (IS_ERR(priv->clk)) 1347eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan return PTR_ERR(priv->clk); 1357eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 1367eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan priv->chip.ops = &clps711x_pwm_ops; 1377eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan priv->chip.dev = &pdev->dev; 1387eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan priv->chip.base = -1; 1397eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan priv->chip.npwm = 2; 1407eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan priv->chip.of_xlate = clps711x_pwm_xlate; 1417eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan priv->chip.of_pwm_n_cells = 1; 1427eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 1437eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan spin_lock_init(&priv->lock); 1447eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 1457eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan platform_set_drvdata(pdev, priv); 1467eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 1477eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan return pwmchip_add(&priv->chip); 1487eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan} 1497eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 1507eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyanstatic int clps711x_pwm_remove(struct platform_device *pdev) 1517eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan{ 1527eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan struct clps711x_chip *priv = platform_get_drvdata(pdev); 1537eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 1547eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan return pwmchip_remove(&priv->chip); 1557eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan} 1567eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 1577eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyanstatic const struct of_device_id __maybe_unused clps711x_pwm_dt_ids[] = { 1587eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan { .compatible = "cirrus,clps711x-pwm", }, 1597eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan { } 1607eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan}; 1617eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander ShiyanMODULE_DEVICE_TABLE(of, clps711x_pwm_dt_ids); 1627eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 1637eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyanstatic struct platform_driver clps711x_pwm_driver = { 1647eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan .driver = { 1657eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan .name = "clps711x-pwm", 1667eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan .owner = THIS_MODULE, 1677eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan .of_match_table = of_match_ptr(clps711x_pwm_dt_ids), 1687eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan }, 1697eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan .probe = clps711x_pwm_probe, 1707eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan .remove = clps711x_pwm_remove, 1717eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan}; 1727eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyanmodule_platform_driver(clps711x_pwm_driver); 1737eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander Shiyan 1747eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander ShiyanMODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); 1757eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander ShiyanMODULE_DESCRIPTION("Cirrus Logic CLPS711X PWM driver"); 1767eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5Alexander ShiyanMODULE_LICENSE("GPL"); 177