diff -drupN a/drivers/pwm/pwm-sunxi.c b/drivers/pwm/pwm-sunxi.c --- a/drivers/pwm/pwm-sunxi.c 1970-01-01 03:00:00.000000000 +0300 +++ b/drivers/pwm/pwm-sunxi.c 2022-06-12 05:28:14.000000000 +0300 @@ -0,0 +1,721 @@ +/* + * drivers/pwm/pwm-sunxi.c + * + * Allwinnertech pulse-width-modulation controller driver + * + * Copyright (C) 2015 AllWinner + * + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/*#include */ +#include +#include +#include +#include +#include +#include + +#include +#include + +#define PWM_DEBUG 0 +#define PWM_NUM_MAX 4 + +#define PWM_PIN_STATE_ACTIVE "active" +#define PWM_PIN_STATE_SLEEP "sleep" + +#define SETMASK(width, shift) ((width?((-1U) >> (32-width)):0) << (shift)) +#define CLRMASK(width, shift) (~(SETMASK(width, shift))) +#define GET_BITS(shift, width, reg) \ + (((reg) & SETMASK(width, shift)) >> (shift)) +#define SET_BITS(shift, width, reg, val) \ + (((reg) & CLRMASK(width, shift)) | (val << (shift))) + +#if PWM_DEBUG +#define pwm_debug(msg...) pr_info +#else +#define pwm_debug(msg...) +#endif + +#if ((defined CONFIG_ARCH_SUN8IW12P1) ||\ + (defined CONFIG_ARCH_SUN8IW17P1) ||\ + (defined CONFIG_ARCH_SUN50IW6P1) ||\ + (defined CONFIG_ARCH_SUN8IW15P1) ||\ + (defined CONFIG_ARCH_SUN50IW3P1)) +#define CLK_GATE_SUPPORT +#endif + +struct sunxi_pwm_config { + unsigned int reg_busy_offset; + unsigned int reg_busy_shift; + unsigned int reg_enable_offset; + unsigned int reg_enable_shift; + unsigned int reg_clk_gating_offset; + unsigned int reg_clk_gating_shift; + unsigned int reg_bypass_offset; + unsigned int reg_bypass_shift; + unsigned int reg_pulse_start_offset; + unsigned int reg_pulse_start_shift; + unsigned int reg_mode_offset; + unsigned int reg_mode_shift; + unsigned int reg_polarity_offset; + unsigned int reg_polarity_shift; + unsigned int reg_period_offset; + unsigned int reg_period_shift; + unsigned int reg_period_width; + unsigned int reg_active_offset; + unsigned int reg_active_shift; + unsigned int reg_active_width; + unsigned int reg_prescal_offset; + unsigned int reg_prescal_shift; + unsigned int reg_prescal_width; + +}; + +struct sunxi_pwm_chip { + struct pwm_chip chip; + void __iomem *base; + struct sunxi_pwm_config *config; +#if defined(CLK_GATE_SUPPORT) + struct clk *pwm_clk; +#endif +}; + +static inline struct sunxi_pwm_chip *to_sunxi_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct sunxi_pwm_chip, chip); +} + +static inline u32 sunxi_pwm_readl(struct pwm_chip *chip, u32 offset) +{ + struct sunxi_pwm_chip *pc = to_sunxi_pwm_chip(chip); + u32 value = 0; + + value = readl(pc->base + offset); + + return value; +} + +static inline u32 sunxi_pwm_writel(struct pwm_chip *chip, u32 offset, u32 value) +{ + struct sunxi_pwm_chip *pc = to_sunxi_pwm_chip(chip); + + writel(value, pc->base + offset); + + return 0; +} + +static int sunxi_pwm_pin_set_state(struct device *dev, char *name) +{ + struct pinctrl *pctl; + struct pinctrl_state *state; + int ret = -1; + + pctl = pinctrl_get(dev); + if (IS_ERR(pctl)) { + dev_err(dev, "pinctrl_get failed!\n"); + ret = PTR_ERR(pctl); + goto exit; + } + + state = pinctrl_lookup_state(pctl, name); + if (IS_ERR(state)) { + dev_err(dev, "pinctrl_lookup_state(%s) failed!\n", name); + ret = PTR_ERR(state); + goto exit; + } + + ret = pinctrl_select_state(pctl, state); + if (ret < 0) { + dev_err(dev, "pinctrl_select_state(%s) failed!\n", name); + goto exit; + } + ret = 0; + +exit: + return ret; +} + +static int sunxi_pwm_get_config(struct platform_device *pdev, + struct sunxi_pwm_config *config) +{ + struct device_node *np = pdev->dev.of_node; + int ret = 0; + + /* read register config */ + ret = of_property_read_u32(np, + "reg_busy_offset", &config->reg_busy_offset); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_busy_offset! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_busy_shift", &config->reg_busy_shift); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_busy_shift! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_enable_offset", &config->reg_enable_offset); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_enable_offset! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_enable_shift", &config->reg_enable_shift); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_enable_shift! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_clk_gating_offset", &config->reg_clk_gating_offset); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_clk_gating_offset! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_clk_gating_shift", &config->reg_clk_gating_shift); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_clk_gating_shift! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_bypass_offset", &config->reg_bypass_offset); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_bypass_offset! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_bypass_shift", &config->reg_bypass_shift); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_bypass_shift! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_pulse_start_offset", &config->reg_pulse_start_offset); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_bypass_offset! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_pulse_start_shift", &config->reg_pulse_start_shift); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_pulse_start_shift! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_mode_offset", &config->reg_mode_offset); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_mode_offset! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_mode_shift", &config->reg_mode_shift); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_mode_shift! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_polarity_offset", &config->reg_polarity_offset); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_polarity_offset! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_polarity_shift", &config->reg_polarity_shift); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_polarity_shift! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_period_offset", &config->reg_period_offset); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_period_offset! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_period_shift", &config->reg_period_shift); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_period_shift! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_period_width", &config->reg_period_width); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_period_width! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_active_offset", &config->reg_active_offset); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_duty_offset! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_active_shift", &config->reg_active_shift); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_duty_shift! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_active_width", &config->reg_active_width); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_duty_width! err=%d\n", ret); + goto err; + } + + ret = of_property_read_u32(np, + "reg_prescal_offset", &config->reg_prescal_offset); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_duty_width! err=%d\n", ret); + goto err; + } + ret = of_property_read_u32(np, + "reg_prescal_shift", &config->reg_prescal_shift); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_duty_width! err=%d\n", ret); + goto err; + } + ret = of_property_read_u32(np, + "reg_prescal_width", &config->reg_prescal_width); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get reg_duty_width! err=%d\n", ret); + goto err; + } +err: + + of_node_put(np); + + return ret; +} + +static int sunxi_pwm_set_polarity(struct pwm_chip *chip, + struct pwm_device *pwm, enum pwm_polarity polarity) +{ + u32 temp; + struct sunxi_pwm_chip *pc = to_sunxi_pwm_chip(chip); + unsigned int reg_offset, reg_shift; + + reg_offset = pc->config[pwm->pwm - chip->base].reg_polarity_offset; + reg_shift = pc->config[pwm->pwm - chip->base].reg_polarity_shift; + temp = sunxi_pwm_readl(chip, reg_offset); + if (polarity == PWM_POLARITY_NORMAL) + temp = SET_BITS(reg_shift, 1, temp, 0); + else + temp = SET_BITS(reg_shift, 1, temp, 1); + + sunxi_pwm_writel(chip, reg_offset, temp); + + return 0; +} + +static int sunxi_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + u32 pre_scal[11][2] = { + /* reg_value clk_pre_div */ + {15, 1}, + {0, 120}, + {1, 180}, + {2, 240}, + {3, 360}, + {4, 480}, + {8, 12000}, + {9, 24000}, + {10, 36000}, + {11, 48000}, + {12, 72000} + }; + u32 freq; + u32 pre_scal_id = 0; + u32 entire_cycles = 256; + u32 active_cycles = 192; + u32 entire_cycles_max = 65536; + u32 temp; + struct sunxi_pwm_chip *pc = to_sunxi_pwm_chip(chip); + unsigned int reg_offset, reg_shift, reg_width; + + reg_offset = pc->config[pwm->pwm - chip->base].reg_bypass_offset; + reg_shift = pc->config[pwm->pwm - chip->base].reg_bypass_shift; + + if (period_ns < 42) { + /* if freq lt 24M, then direct output 24M clock */ + temp = sunxi_pwm_readl(chip, reg_offset); + temp = SET_BITS(reg_shift, 1, temp, 1); + sunxi_pwm_writel(chip, reg_offset, temp); + return 0; + } + + /* disable bypass function */ + temp = sunxi_pwm_readl(chip, reg_offset); + temp = SET_BITS(reg_shift, 1, temp, 0); + sunxi_pwm_writel(chip, reg_offset, temp); + + if (period_ns < 10667) + freq = 93747; + else if (period_ns > 1000000000) + freq = 1; + else + freq = 1000000000 / period_ns; + + /* clock source rate is 24Mhz */ + entire_cycles = 24000000 / freq / pre_scal[pre_scal_id][1]; + + while (entire_cycles > entire_cycles_max) { + pre_scal_id++; + + if (pre_scal_id > 10) + break; + + entire_cycles = 24000000 / freq / pre_scal[pre_scal_id][1]; + } + + if (period_ns < 5*100*1000) + active_cycles = (duty_ns * entire_cycles + (period_ns/2)) + / period_ns; + else if (period_ns >= 5*100*1000 && period_ns < 6553500) + active_cycles = ((duty_ns / 100) * entire_cycles + + (period_ns / 2 / 100)) / (period_ns/100); + else + active_cycles = ((duty_ns / 10000) * entire_cycles + + (period_ns / 2 / 10000)) / (period_ns/10000); + + /* config prescal */ + reg_offset = pc->config[pwm->pwm - chip->base].reg_prescal_offset; + reg_shift = pc->config[pwm->pwm - chip->base].reg_prescal_shift; + reg_width = pc->config[pwm->pwm - chip->base].reg_prescal_width; + + temp = sunxi_pwm_readl(chip, reg_offset); + temp = SET_BITS(reg_shift, reg_width, temp, (pre_scal[pre_scal_id][0])); + sunxi_pwm_writel(chip, reg_offset, temp); + + /* config active cycles */ + reg_offset = pc->config[pwm->pwm - chip->base].reg_active_offset; + reg_shift = pc->config[pwm->pwm - chip->base].reg_active_shift; + reg_width = pc->config[pwm->pwm - chip->base].reg_active_width; + + temp = sunxi_pwm_readl(chip, reg_offset); + temp = SET_BITS(reg_shift, reg_width, temp, (active_cycles)); + sunxi_pwm_writel(chip, reg_offset, temp); + + /* config period cycles */ + reg_offset = pc->config[pwm->pwm - chip->base].reg_period_offset; + reg_shift = pc->config[pwm->pwm - chip->base].reg_period_shift; + reg_width = pc->config[pwm->pwm - chip->base].reg_period_width; + temp = sunxi_pwm_readl(chip, reg_offset); + temp = SET_BITS(reg_shift, reg_width, temp, (entire_cycles - 1)); + + sunxi_pwm_writel(chip, reg_offset, temp); + + return 0; +} + +static int sunxi_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + u32 value = 0, index = 0; + struct sunxi_pwm_chip *pc = to_sunxi_pwm_chip(chip); + unsigned int reg_offset, reg_shift; + struct device_node *sub_np; + struct platform_device *pwm_pdevice; + + index = pwm->pwm - chip->base; + sub_np = of_parse_phandle(chip->dev->of_node, "pwms", index); + if (IS_ERR_OR_NULL(sub_np)) { + pr_err("%s: can't parse \"pwms\" property\n", __func__); + return -ENODEV; + } + pwm_pdevice = of_find_device_by_node(sub_np); + if (IS_ERR_OR_NULL(pwm_pdevice)) { + pr_err("%s: can't parse pwm device\n", __func__); + return -ENODEV; + } + sunxi_pwm_pin_set_state(&pwm_pdevice->dev, PWM_PIN_STATE_ACTIVE); + + /* enable clk for pwm controller */ + reg_offset = pc->config[pwm->pwm - chip->base].reg_clk_gating_offset; + reg_shift = pc->config[pwm->pwm - chip->base].reg_clk_gating_shift; + value = sunxi_pwm_readl(chip, reg_offset); + value = SET_BITS(reg_shift, 1, value, 1); + sunxi_pwm_writel(chip, reg_offset, value); + + /* enable pwm controller */ + reg_offset = pc->config[pwm->pwm - chip->base].reg_enable_offset; + reg_shift = pc->config[pwm->pwm - chip->base].reg_enable_shift; + value = sunxi_pwm_readl(chip, reg_offset); + value = SET_BITS(reg_shift, 1, value, 1); + sunxi_pwm_writel(chip, reg_offset, value); + + return 0; +} + +static void sunxi_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + u32 value = 0, index = 0; + struct sunxi_pwm_chip *pc = to_sunxi_pwm_chip(chip); + unsigned int reg_offset, reg_shift; + struct device_node *sub_np; + struct platform_device *pwm_pdevice; + + index = pwm->pwm - chip->base; + sub_np = of_parse_phandle(chip->dev->of_node, "pwms", index); + if (IS_ERR_OR_NULL(sub_np)) { + pr_err("%s: can't parse \"pwms\" property\n", __func__); + return; + } + pwm_pdevice = of_find_device_by_node(sub_np); + if (IS_ERR_OR_NULL(pwm_pdevice)) { + pr_err("%s: can't parse pwm device\n", __func__); + return; + } + + /* disable pwm controller */ + reg_offset = pc->config[pwm->pwm - chip->base].reg_enable_offset; + reg_shift = pc->config[pwm->pwm - chip->base].reg_enable_shift; + value = sunxi_pwm_readl(chip, reg_offset); + value = SET_BITS(reg_shift, 1, value, 0); + sunxi_pwm_writel(chip, reg_offset, value); + + /* disable pwm controller */ + reg_offset = pc->config[pwm->pwm - chip->base].reg_clk_gating_offset; + reg_shift = pc->config[pwm->pwm - chip->base].reg_clk_gating_shift; + value = sunxi_pwm_readl(chip, reg_offset); + value = SET_BITS(reg_shift, 1, value, 0); + sunxi_pwm_writel(chip, reg_offset, value); + + sunxi_pwm_pin_set_state(&pwm_pdevice->dev, PWM_PIN_STATE_SLEEP); +} + +static struct pwm_ops sunxi_pwm_ops = { + .config = sunxi_pwm_config, + .enable = sunxi_pwm_enable, + .disable = sunxi_pwm_disable, + .set_polarity = sunxi_pwm_set_polarity, + .owner = THIS_MODULE, +}; + +static int sunxi_pwm_probe(struct platform_device *pdev) +{ + int ret; + struct sunxi_pwm_chip *pwm; + struct device_node *np = pdev->dev.of_node; + int i; + struct platform_device *pwm_pdevice; + struct device_node *sub_np; + + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); + if (!pwm) { + ret = -ENOMEM; + dev_err(&pdev->dev, "failed to allocate memory!\n"); + return ret; + } + + /* io map pwm base */ + pwm->base = (void __iomem *)of_iomap(pdev->dev.of_node, 0); + if (!pwm->base) { + dev_err(&pdev->dev, "unable to map pwm registers\n"); + ret = -EINVAL; + goto err_iomap; + } + + /* read property pwm-number */ + ret = of_property_read_u32(np, "pwm-number", &pwm->chip.npwm); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get pwm number: %d, force to one!\n", ret); + /* force to one pwm if read property fail */ + pwm->chip.npwm = 1; + } + + /* read property pwm-base */ + ret = of_property_read_u32(np, "pwm-base", &pwm->chip.base); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to get pwm-base: %d, force to -1 !\n", ret); + /* force to one pwm if read property fail */ + pwm->chip.base = -1; + } + pwm->chip.dev = &pdev->dev; + pwm->chip.ops = &sunxi_pwm_ops; + + /* add pwm chip to pwm-core */ + ret = pwmchip_add(&pwm->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + goto err_add; + } + platform_set_drvdata(pdev, pwm); + + pwm->config = devm_kzalloc(&pdev->dev, + sizeof(*pwm->config) * pwm->chip.npwm, GFP_KERNEL); + if (!pwm->config) { + ret = -ENOMEM; + dev_err(&pdev->dev, "failed to allocate memory!\n"); + goto err_alloc; + } + + for (i = 0; i < pwm->chip.npwm; i++) { + sub_np = of_parse_phandle(np, "pwms", i); + if (IS_ERR_OR_NULL(sub_np)) { + pr_err("%s: can't parse \"pwms\" property\n", + __func__); + return -EINVAL; + } + + pwm_pdevice = of_find_device_by_node(sub_np); + ret = sunxi_pwm_get_config(pwm_pdevice, &pwm->config[i]); + if (ret) + goto err_get_config; + } + +#if defined(CLK_GATE_SUPPORT) + pwm->pwm_clk = of_clk_get(pdev->dev.of_node, 0); + if (IS_ERR_OR_NULL(pwm->pwm_clk)) { + pr_err("%s: can't get pwm clk\n", __func__); + return -EINVAL; + } + clk_prepare_enable(pwm->pwm_clk); +#endif + return 0; + +err_get_config: +err_alloc: + pwmchip_remove(&pwm->chip); +err_add: + iounmap(pwm->base); +err_iomap: + return ret; +} + +static int sunxi_pwm_remove(struct platform_device *pdev) +{ + struct sunxi_pwm_chip *pwm = platform_get_drvdata(pdev); +#if defined CLK_GATE_SUPPORT + clk_disable(pwm->pwm_clk); +#endif + return pwmchip_remove(&pwm->chip); +} + +static int sunxi_pwm_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int sunxi_pwm_resume(struct platform_device *pdev) +{ + return 0; +} + +#if !defined(CONFIG_OF) +struct platform_device sunxi_pwm_device = { + .name = "sunxi_pwm", + .id = -1, +}; +#else +static const struct of_device_id sunxi_pwm_match[] = { + { .compatible = "allwinner,sunxi-pwm", }, + { .compatible = "allwinner,sunxi-s_pwm", }, + {}, +}; +#endif + +static struct platform_driver sunxi_pwm_driver = { + .probe = sunxi_pwm_probe, + .remove = sunxi_pwm_remove, + .suspend = sunxi_pwm_suspend, + .resume = sunxi_pwm_resume, + .driver = { + .name = "sunxi_pwm", + .owner = THIS_MODULE, + .of_match_table = sunxi_pwm_match, + }, +}; + +static int __init pwm_module_init(void) +{ + int ret = 0; + + pr_info("pwm module init!\n"); + +#if !defined(CONFIG_OF) + ret = platform_device_register(&sunxi_pwm_device); +#endif + if (ret == 0) + ret = platform_driver_register(&sunxi_pwm_driver); + + return ret; +} + +static void __exit pwm_module_exit(void) +{ + pr_info("pwm module exit!\n"); + + platform_driver_unregister(&sunxi_pwm_driver); +#if !defined(CONFIG_OF) + platform_device_unregister(&sunxi_pwm_device); +#endif +} + +subsys_initcall(pwm_module_init); +module_exit(pwm_module_exit); + +MODULE_AUTHOR("tyle"); +MODULE_DESCRIPTION("pwm driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sunxi-pwm");