/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include #include #include #include #include #include #include #include #include #include #include "ipq-adss.h" extern unsigned int _adss_audio_read(unsigned int off, const char *regname __maybe_unused) __attribute__((weak)); extern void _adss_audio_write( unsigned int val, unsigned int off, const char *regname __maybe_unused) __attribute__((weak)); #define SRC_FREQ (200*1000*1000) #define MAX_PWM_DEVICES 4 /* The default period and duty cycle values to be configured. */ #define DEFAULT_PERIOD_NS 10 #define DEFAULT_DUTY_CYCLE_NS 5 /* The frequency range supported is 762Hz to 100MHz. */ #define MIN_PERIOD_NS 10 #define MAX_PERIOD_NS 1312335 /* The max value specified for each field is based on the number of bits * in the pwm control regitser for that filed */ #define MAX_PRE_DIV 0xFF #define MAX_PWM_DIV 0x3FF #define MAX_HI_DUR 0x7FF /* Enable bit is set to enable output toggling in pwm device. * Update bit is set to reflect the changed divider and high duration * values in register. Update bit is auto cleared after the update */ #define PWM_ENABLE 0x80000000 #define PWM_UPDATE 0x40000000 #define PWM_CTRL_PRE_DIV_SHIFT 22 #define PWM_CTRL_DIV_SHIFT 0 #define PWM_CTRL_HI_SHIFT 10 /* The frequency range supported is 1Hz to 100MHz in V2. */ #define MIN_PERIOD_NS_V2 10 #define MAX_PERIOD_NS_V2 1000000000 /* The max value specified for each field is based on the number of bits * in the pwm control regitser for that filed in V2 */ #define MAX_PWM_CFG_V2 0xFFFF #define PWM_CTRL_HI_SHIFT_V2 16 #define PWM0_CTRL_REG0 0x0 #define PWM0_CTRL_REG1 0x4 #define PWM1_CTRL_REG0 0x8 #define PWM1_CTRL_REG1 0xc #define PWM2_CTRL_REG0 0x10 #define PWM2_CTRL_REG1 0x14 #define PWM3_CTRL_REG0 0x18 #define PWM3_CTRL_REG1 0x1C #define PWM_CFG_REG0 0 /*PWM_DIV PWM_HI*/ #define PWM_CFG_REG1 1 /*ENABLE UPDATE PWM_PRE_DIV*/ enum pwm_version { PWM_V1, PWM_V2, }; struct ipq_pwm_ops { void (*pwm_writel)(struct pwm_device*, uint32_t, uint32_t); uint32_t (*pwm_readl)(struct pwm_device*, uint32_t); u32 max_pre_div; u32 max_pwm_div; u32 max_period_ns; u32 min_period_ns; enum pwm_version version; }; struct ipq4019_pwm_chip { struct pwm_chip chip; struct clk *clk; void __iomem *mem; struct ipq_pwm_ops *ops; }; static ssize_t count; static uint32_t used_pwm[MAX_PWM_DEVICES]; static int avm_rate = 0; static const uint32_t pwm_ctrl_register[] = { ADSS_GLB_PWM0_CTRL_REG, ADSS_GLB_PWM1_CTRL_REG, ADSS_GLB_PWM2_CTRL_REG, ADSS_GLB_PWM3_CTRL_REG, }; static uint32_t avm_pwm_shadow[MAX_PWM_DEVICES]; static uint32_t avm_pwm_period[MAX_PWM_DEVICES]; static uint32_t avm_pwm_ctrl_buffer[MAX_PWM_DEVICES]; static uint32_t avm_pwm_close_diff[MAX_PWM_DEVICES]; void avm_pwm_configure_channel(unsigned int channel, unsigned int duty_ns, unsigned int period_ns){ unsigned long freq; int pre_div, close_pre_div, close_pwm_div; int pwm_div = MAX_PWM_DIV; long diff; unsigned long hi_dur; unsigned long ctrl_reg_val = 0; unsigned long min_diff = avm_rate; if(avm_rate == 0){ pr_err("[%s] rate not set\n", __func__); return; } /* freq in Hz for period in nano second*/ freq = NSEC_PER_SEC / period_ns; if (period_ns > MAX_PERIOD_NS || period_ns < MIN_PERIOD_NS) { pr_err("PWM Frequency range supported is 762Hz to 100MHz\n" "Switching to default configuration values\n"); period_ns = DEFAULT_PERIOD_NS; duty_ns = DEFAULT_DUTY_CYCLE_NS; } if(avm_pwm_period[channel] != period_ns){ do { pre_div = 0; while (pre_div <= MAX_PRE_DIV) { diff = avm_rate - (freq * (pre_div + 1) * (pwm_div + 1)); if (diff < 0) diff = -diff; if (!diff) { close_pre_div = pre_div; close_pwm_div = pwm_div; break; } if (diff < min_diff) { min_diff = diff; close_pre_div = pre_div; close_pwm_div = pwm_div; } pre_div++; } pwm_div--; } while (pwm_div >= 0 && diff); ctrl_reg_val |= ((close_pre_div & MAX_PRE_DIV) << PWM_CTRL_PRE_DIV_SHIFT); ctrl_reg_val |= ((close_pwm_div & MAX_PWM_DIV) << PWM_CTRL_DIV_SHIFT); avm_pwm_ctrl_buffer[channel] = ctrl_reg_val; avm_pwm_close_diff[channel] = close_pwm_div; } else{ ctrl_reg_val = avm_pwm_ctrl_buffer[channel]; } avm_pwm_period[channel] = period_ns; /* config divider values for the closest possible frequency */ /* high duration = pwm duty * ( pwm_div +1) * pwm duty = duty_ns / period_ns */ hi_dur = ((avm_pwm_close_diff[channel] + 1) * duty_ns) / period_ns; ctrl_reg_val |= ((hi_dur & MAX_HI_DUR) << PWM_CTRL_HI_SHIFT); avm_pwm_shadow[channel] = ctrl_reg_val | (PWM_ENABLE | PWM_UPDATE); return; } void avm_pwm_update(void){ int channel; if(!IS_ERR(&_adss_audio_read)){ if(!IS_ERR(&_adss_audio_write)){ for(channel = 0; channel < MAX_PWM_DEVICES; channel++){ //enable _adss_audio_write(avm_pwm_shadow[channel], pwm_ctrl_register[channel], 0); } } } } int avm_pwm_request_channel(unsigned int channel,unsigned int duty_ns, unsigned int period_ns){ if(channel >= MAX_PWM_DEVICES){ pr_err("[%s] channel %d > MAX_PWM_DEVICES %d\n", __func__, channel, MAX_PWM_DEVICES); return -ENODEV; } if (!used_pwm[channel]){ pr_err("[%s] channel %d not usable\n", __func__, channel); return -EINVAL; } avm_pwm_configure_channel(channel, duty_ns, period_ns); return 0; } static const uint32_t pwm_ctrl_register_v2[] = { PWM0_CTRL_REG0, PWM0_CTRL_REG1, PWM1_CTRL_REG0, PWM1_CTRL_REG1, PWM2_CTRL_REG0, PWM2_CTRL_REG1, PWM3_CTRL_REG0, PWM3_CTRL_REG1, }; static struct ipq4019_pwm_chip *to_ipq4019_pwm_chip(struct pwm_chip *chip) { return container_of(chip, struct ipq4019_pwm_chip, chip); } #ifdef CONFIG_SND_SOC_IPQ_ADSS static void pwm_writel_v1(struct pwm_device *pwm, uint32_t val, uint32_t cfg_reg) { ipq_audio_adss_writel(val, pwm_ctrl_register[pwm->hwpwm]); } static uint32_t pwm_readl_v1(struct pwm_device *pwm, uint32_t cfg_reg) { return ipq_audio_adss_readl(pwm_ctrl_register[pwm->hwpwm]); } #elif CONFIG_SND_SOC_IPQ_AVM static void pwm_writel_v1(struct pwm_device *pwm, uint32_t val, uint32_t cfg_reg) { _adss_audio_write(val, pwm_ctrl_register[pwm->hwpwm], 0); } static uint32_t pwm_readl_v1(struct pwm_device *pwm, uint32_t cfg_reg) { return _adss_audio_read(pwm_ctrl_register[pwm->hwpwm], 0); } #else static void pwm_writel_v1(struct pwm_device *pwm, uint32_t val, uint32_t cfg_reg) { return; } static uint32_t pwm_readl_v1(struct pwm_device *pwm, uint32_t cfg_reg) { return 0; } #endif static void pwm_writel_v2(struct pwm_device *pwm, uint32_t val, uint32_t cfg_reg) { struct ipq4019_pwm_chip *ipq_chip = to_ipq4019_pwm_chip(pwm->chip); uint32_t offset; offset = pwm_ctrl_register_v2[(pwm->hwpwm * 2) + cfg_reg]; writel(val, ipq_chip->mem + offset); } static uint32_t pwm_readl_v2(struct pwm_device *pwm, uint32_t cfg_reg) { struct ipq4019_pwm_chip *ipq_chip = to_ipq4019_pwm_chip(pwm->chip); uint32_t offset; offset = pwm_ctrl_register_v2[(pwm->hwpwm * 2) + cfg_reg]; return readl(ipq_chip->mem + offset); } static void calc_div_and_duty(struct pwm_chip *chip, int32_t *close_pre_div_out, uint64_t *close_pwm_div_out, int32_t period_ns, int32_t duty_ns) { struct ipq4019_pwm_chip *ipq4019_chip = to_ipq4019_pwm_chip(chip); uint32_t freq; int32_t pre_div, close_pre_div; uint64_t close_pwm_div; uint64_t pwm_div; int64_t diff; uint32_t rate = clk_get_rate(ipq4019_chip->clk); uint32_t min_diff = rate; uint64_t fin_ps; /* freq in Hz for period in nano second*/ freq = NSEC_PER_SEC / period_ns; fin_ps = (uint64_t)NSEC_PER_SEC * 1000; do_div(fin_ps, rate); close_pre_div = ipq4019_chip->ops->max_pre_div; close_pwm_div = ipq4019_chip->ops->max_pwm_div; pr_debug("pre_div: %x, pwm_div: %llx, period_ns: %d, duty_ns: %d\n", close_pre_div, close_pwm_div, period_ns, duty_ns); for (pre_div = 0; pre_div <= ipq4019_chip->ops->max_pre_div; pre_div++) { pwm_div = DIV_ROUND_CLOSEST_ULL((uint64_t)period_ns * 1000, fin_ps * (pre_div + 1)); pwm_div--; if (pwm_div <= ipq4019_chip->ops->max_pwm_div) { diff = (uint64_t)rate - ((uint64_t)freq * (pre_div + 1) * (pwm_div + 1)); if (diff < 0) diff = -diff; if (!diff) { close_pre_div = pre_div; close_pwm_div = pwm_div; break; } if (diff < min_diff) { min_diff = diff; close_pre_div = pre_div; close_pwm_div = pwm_div; } } } *close_pre_div_out = close_pre_div; *close_pwm_div_out = close_pwm_div; pr_debug("pre_div: %x, pwm_div: %llx\n", *close_pre_div_out, *close_pwm_div_out); } static void config_div_and_duty(struct pwm_device *pwm, int32_t pre_div, uint64_t pwm_div, int32_t period_ns, int32_t duty_ns) { uint32_t hi_dur; uint64_t quotient; uint32_t ctrl_reg_val = 0; struct ipq4019_pwm_chip *ipq_chip = to_ipq4019_pwm_chip(pwm->chip); pr_debug("pre_div: %x, pwm_div: %llx, period_ns: %d, duty_ns: %d\n", pre_div, pwm_div, period_ns, duty_ns); if (pwm->polarity == PWM_POLARITY_INVERSED) duty_ns = period_ns - duty_ns; /* high duration = pwm duty * ( pwm div + 1) * pwm duty = duty_ns / period_ns */ quotient = (pwm_div + 1) * duty_ns; do_div(quotient, period_ns); hi_dur = quotient; if (ipq_chip->ops->version == PWM_V1) { ctrl_reg_val |= ((pre_div & ipq_chip->ops->max_pre_div) << PWM_CTRL_PRE_DIV_SHIFT); ctrl_reg_val |= ((hi_dur & MAX_HI_DUR) << PWM_CTRL_HI_SHIFT); ctrl_reg_val |= ((pwm_div & ipq_chip->ops->max_pwm_div) << PWM_CTRL_DIV_SHIFT); } else { ctrl_reg_val |= ((hi_dur & MAX_PWM_CFG_V2) << PWM_CTRL_HI_SHIFT_V2); ctrl_reg_val |= (pwm_div & ipq_chip->ops->max_pwm_div); pr_debug("PWM_CFG_REG0: %x\n", ctrl_reg_val); ipq_chip->ops->pwm_writel(pwm, ctrl_reg_val, PWM_CFG_REG0); ctrl_reg_val = pre_div & ipq_chip->ops->max_pre_div; pr_debug("PWM_CFG_REG1: %x\n", ctrl_reg_val); } ipq_chip->ops->pwm_writel(pwm, ctrl_reg_val, PWM_CFG_REG1); } static void set_update_and_enable(struct pwm_chip *chip, struct pwm_device *pwm) { struct ipq4019_pwm_chip *ipq_chip = to_ipq4019_pwm_chip(pwm->chip); unsigned long ctrl_reg_val; ctrl_reg_val = ipq_chip->ops->pwm_readl(pwm, PWM_CFG_REG1); ctrl_reg_val |= (PWM_ENABLE | PWM_UPDATE); pr_debug("PWM_CFG_REG1: %lx\n", ctrl_reg_val); ipq_chip->ops->pwm_writel(pwm, ctrl_reg_val, PWM_CFG_REG1); } static int ipq4019_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) { struct ipq4019_pwm_chip *ipq_chip = to_ipq4019_pwm_chip(pwm->chip); int32_t close_pre_div; uint64_t close_pwm_div; pr_debug("%s\n", __func__); set_update_and_enable(chip, pwm); /* JZ-95202: to turn on LED on Maple after boot, * both config and enable need to be called again after an enable * Hawkeye also has this problem, which is V1. * So Dakota gets the ugly fix, too. Boo. */ calc_div_and_duty(chip, &close_pre_div, &close_pwm_div, pwm->period, pwm->duty_cycle); pr_debug("pre_div: %x, pwm_div: %llx\n", close_pre_div, close_pwm_div); /* config divider values for the closest possible frequency */ config_div_and_duty(pwm, close_pre_div, close_pwm_div, pwm->period, pwm->duty_cycle); set_update_and_enable(chip, pwm); return 0; } static void ipq4019_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) { struct ipq4019_pwm_chip *ipq_chip = to_ipq4019_pwm_chip(pwm->chip); unsigned long ctrl_reg_val; pr_debug("%s\n", __func__); ctrl_reg_val = ipq_chip->ops->pwm_readl(pwm, PWM_CFG_REG1); ctrl_reg_val &= ~PWM_ENABLE; ipq_chip->ops->pwm_writel(pwm, ctrl_reg_val, PWM_CFG_REG1); } static int ipq4019_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { struct ipq4019_pwm_chip *ipq4019_chip = to_ipq4019_pwm_chip(chip); int32_t close_pre_div; uint64_t close_pwm_div; pr_debug("%s called with period_ns %d and duty_ns %d\n", __func__, period_ns, duty_ns); if (period_ns > ipq4019_chip->ops->max_period_ns || period_ns < ipq4019_chip->ops->min_period_ns) { pr_err("PWM Frequency range supported is %dHz to %dHz\n" "Switching to default configuration values\n", (int)NSEC_PER_SEC / ipq4019_chip->ops->max_period_ns, (int)NSEC_PER_SEC / ipq4019_chip->ops->min_period_ns); period_ns = DEFAULT_PERIOD_NS; duty_ns = DEFAULT_DUTY_CYCLE_NS; } pwm->period = period_ns; pwm->duty_cycle = duty_ns; pr_debug("period_ns %d and duty_ns %d\n", period_ns, duty_ns); calc_div_and_duty(chip, &close_pre_div, &close_pwm_div, period_ns, duty_ns); /* config divider values for the closest possible frequency */ config_div_and_duty(pwm, close_pre_div, close_pwm_div, period_ns, duty_ns); pr_debug("testing if pwm %d should be enabled\n", pwm->hwpwm); if (pwm_is_enabled(pwm)) { pr_debug("pwm %d should be enabled\n", pwm->hwpwm); ipq4019_pwm_enable(chip, pwm); } else { pr_debug("pwm %d should NOT be enabled\n", pwm->hwpwm); } pr_debug("%s finished\n", __func__); return 0; } static int ipq4019_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) { if (!used_pwm[pwm->hwpwm]) return -EINVAL; return 0; } static void ipq4019_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) { ipq4019_pwm_disable(chip, pwm); } static int ipq4019_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm, enum pwm_polarity polarity) { /* * The pwm-core will only set the polarity after we've returned, but we * do inversion in software, therefore we need to set it now. */ pwm->polarity = polarity; /* Update the config with the new divider */ ipq4019_pwm_config(chip, pwm, pwm->duty_cycle, pwm->period); return 0; } static struct pwm_ops ipq4019_pwm_ops = { .request = ipq4019_pwm_request, .free = ipq4019_pwm_free, .config = ipq4019_pwm_config, .set_polarity = ipq4019_pwm_set_polarity, .enable = ipq4019_pwm_enable, .disable = ipq4019_pwm_disable, .owner = THIS_MODULE, }; static int ipq4019_pwm_probe(struct platform_device *pdev) { struct ipq4019_pwm_chip *pwm; struct device *dev; unsigned int base_index; int ret; const void *dev_data; unsigned long src_freq = SRC_FREQ; struct resource *res = NULL; dev_dbg(dev, "%s called\n", __func__); dev = &pdev->dev; pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL); if (!pwm) { dev_err(dev, "failed to allocate memory\n"); return -ENOMEM; } platform_set_drvdata(pdev, pwm); dev_data = of_device_get_match_data(dev); if (!dev_data) { dev_err(&pdev->dev, "failed to get device data\n"); return -ENODEV; } pwm->ops = (struct ipq_pwm_ops*) dev_data; of_property_read_u64(dev->of_node, "src-freq", (u64 *)src_freq); pwm->clk = devm_clk_get(dev, "core"); if (!IS_ERR(pwm->clk)) { ret = clk_set_rate(pwm->clk, src_freq); if (ret) return ret; ret = clk_prepare_enable(pwm->clk); if (ret) return ret; } avm_rate = clk_get_rate(pwm->clk); if (pwm->ops->version == PWM_V2) { res = platform_get_resource(pdev, IORESOURCE_MEM, 0); pwm->mem = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(pwm->mem)) return PTR_ERR(pwm->mem); } if (of_property_read_u32(dev->of_node, "pwm-base-index", &base_index)) base_index = 0; count = of_property_count_u32_elems(dev->of_node, "used-pwm-indices"); if (of_property_read_u32_array(dev->of_node, "used-pwm-indices", used_pwm, count)) return -EINVAL; pwm->chip.dev = dev; pwm->chip.ops = &ipq4019_pwm_ops; pwm->chip.base = base_index; pwm->chip.npwm = count; pwm->chip.of_xlate = of_pwm_xlate_with_flags; pwm->chip.of_pwm_n_cells = 3; ret = pwmchip_add(&pwm->chip); if (ret < 0) { dev_err(dev, "pwmchip_add() failed: %d\n", ret); return ret; } dev_dbg(dev, "%s finished\n", __func__); return 0; } static int ipq4019_pwm_remove(struct platform_device *pdev) { struct ipq4019_pwm_chip *pwm = platform_get_drvdata(pdev); return pwmchip_remove(&pwm->chip); } static const struct ipq_pwm_ops ops_v1 = { .pwm_writel = pwm_writel_v1, .pwm_readl = pwm_readl_v1, .max_pre_div = MAX_PRE_DIV, .max_pwm_div = MAX_PWM_DIV, .max_period_ns = MAX_PERIOD_NS, .min_period_ns = MIN_PERIOD_NS, .version = PWM_V1 }; static const struct ipq_pwm_ops ops_v2 = { .pwm_writel = pwm_writel_v2, .pwm_readl = pwm_readl_v2, .max_pre_div = MAX_PWM_CFG_V2, .max_pwm_div = MAX_PWM_CFG_V2, .max_period_ns = MAX_PERIOD_NS_V2, .min_period_ns = MIN_PERIOD_NS_V2, .version = PWM_V2 }; static const struct of_device_id pwm_msm_dt_match[] = { { .compatible = "qca,ipq4019-pwm", .data = &ops_v1 }, { .compatible = "qca,ipq6018-pwm", .data = &ops_v2 }, {} }; MODULE_DEVICE_TABLE(of, pwm_msm_dt_match); static struct platform_driver ipq4019_pwm_driver = { .driver = { .name = "qca,ipq4019-pwm", .owner = THIS_MODULE, .of_match_table = pwm_msm_dt_match, }, .probe = ipq4019_pwm_probe, .remove = ipq4019_pwm_remove, }; module_platform_driver(ipq4019_pwm_driver); MODULE_LICENSE("Dual BSD/GPL");