firmware/br-ext-chip-ingenic/board/t40/kernel/patches/00000-drivers_pwm_pwm-ingen...

492 lines
12 KiB
Diff

diff -drupN a/drivers/pwm/pwm-ingenic.c b/drivers/pwm/pwm-ingenic.c
--- a/drivers/pwm/pwm-ingenic.c 1970-01-01 03:00:00.000000000 +0300
+++ b/drivers/pwm/pwm-ingenic.c 2022-06-09 05:02:33.000000000 +0300
@@ -0,0 +1,487 @@
+/*
+ * Copyright (c) 2007 Ben Dooks
+ * Copyright (c) 2008 Simtec Electronics
+ * Ben Dooks <ben@simtec.co.uk>, <ben-linux@fluff.org>
+ * Copyright (c) 2013 Tomasz Figa <tomasz.figa@gmail.com>
+ *
+ * PWM driver for Samsung SoCs
+ *
+ * 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.
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/export.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/time.h>
+
+#include <linux/mfd/core.h>
+#include <linux/mfd/ingenic-tcu.h>
+
+/**
+ * struct ingenic_pwm_channel - private data of PWM channel
+ * @period_ns: current period in nanoseconds programmed to the hardware
+ * @duty_ns: current duty time in nanoseconds programmed to the hardware
+ */
+struct ingenic_pwm_channel {
+ struct ingenic_tcu_chn *chan;
+ u32 period_ns;
+ u32 duty_ns;
+};
+
+/**
+ * struct ingenic_pwm_chip - private data of PWM chip
+ * @chip: generic PWM chip
+ * @rtc_clk: external RTC clock(can be ERR_PTR if not present)
+ * @ext_clk: external EXT clock(can be ERR_PTR if not present)
+ * @output_mask: output status for all channels - one bit per channel
+ */
+struct ingenic_pwm_chip {
+ struct pwm_chip chip;
+ struct clk *rtc_clk;
+ struct clk *ext_clk;
+ u32 output_mask;
+};
+
+static inline
+struct ingenic_pwm_chip *to_ingenic_pwm_chip(struct pwm_chip *chip)
+{
+ return container_of(chip, struct ingenic_pwm_chip, chip);
+}
+static inline
+struct ingenic_tcu_chn *to_ingenic_tcu_channel(struct pwm_device *pwm)
+{
+ return ((struct ingenic_pwm_channel *)pwm_get_chip_data(pwm))->chan;
+}
+
+static int pwm_get_period_num(struct ingenic_pwm_chip *chip,
+ struct ingenic_tcu_chn *tcu_chan, int period_ns)
+{
+ unsigned long long rate;
+ unsigned long long tmp;
+ int div = 0;
+ int period = 0;
+
+ if(tcu_chan->clk_src == TCU_CLKSRC_EXT) {
+ rate = (unsigned long long)clk_get_rate(chip->ext_clk);
+ } else if(tcu_chan->clk_src == TCU_CLKSRC_RTC) {
+ rate = (unsigned long long)clk_get_rate(chip->ext_clk);
+ } else {
+ printk("not support clk soruce\n");
+ return -EINVAL;
+ }
+
+ tmp = rate * period_ns;
+ do_div(tmp, 1000000000);
+ period = tmp;
+
+ while (period > 0xffff && div < 6) {
+ period >>= 2;
+ ++div;
+ }
+
+ if (div == 6)
+ return -EINVAL;
+
+ tcu_chan->full_num = period;
+ tcu_chan->clk_div = div;
+ return period;
+}
+static int pwm_ingenic_get_period(struct ingenic_pwm_chip *chip,
+ struct pwm_device *pwm, int period_ns)
+{
+ struct ingenic_tcu_chn *tcu_chan;
+ int period;
+
+ tcu_chan = to_ingenic_tcu_channel(pwm);
+ /*
+ * Compare minimum PWM frequency that can be achieved with possible
+ * divider settings and choose the lowest divisor that can generate
+ * frequencies lower than requested.
+ */
+ tcu_chan->clk_src = TCU_CLKSRC_EXT;
+ period = pwm_get_period_num(chip, tcu_chan, period_ns);
+ if(period < 0) {
+ tcu_chan->clk_src = TCU_CLKSRC_RTC;
+ period = pwm_get_period_num(chip, tcu_chan, period_ns);
+ if(period < 0) {
+ dev_err(chip->chip.dev,"can not find right div\n");
+ }
+ }
+
+ return period;
+}
+
+static int pwm_ingenic_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct ingenic_pwm_chip *our_chip = to_ingenic_pwm_chip(chip);
+ struct ingenic_pwm_channel *our_chan;
+ struct mfd_cell *cell;
+
+ if (!(our_chip->output_mask & BIT(pwm->hwpwm))) {
+ dev_warn(chip->dev,
+ "tried to request PWM channel %d without output\n",
+ pwm->hwpwm);
+ return -EINVAL;
+ }
+
+ cell = request_cell(pwm->hwpwm);
+ if(!cell){
+ dev_err(chip->dev, "tried to request tcu channel %d busy\n",
+ pwm->hwpwm);
+ return -EINVAL;
+ }
+
+ our_chan = devm_kzalloc(chip->dev, sizeof(*our_chan), GFP_KERNEL);
+ if (!our_chan)
+ return -ENOMEM;
+
+ our_chan->chan = cell->platform_data;
+ our_chan->chan->enable(cell->id);
+
+ pwm_set_chip_data(pwm, our_chan);
+
+ return 0;
+}
+
+static void pwm_ingenic_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct ingenic_pwm_channel *our_chan;
+ int id;
+
+ our_chan = pwm_get_chip_data(pwm);
+
+ id = pwm->hwpwm;
+ our_chan->chan->disable(id);
+ free_cell(id);
+ devm_kfree(chip->dev, our_chan);
+ pwm_set_chip_data(pwm, NULL);
+}
+
+static int pwm_ingenic_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct ingenic_tcu_chn *tcu_chan;
+ tcu_chan = to_ingenic_tcu_channel(pwm);
+ ingenic_tcu_counter_begin(tcu_chan);
+
+ return 0;
+}
+
+static void pwm_ingenic_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct ingenic_tcu_chn *tcu_chan;
+ tcu_chan = to_ingenic_tcu_channel(pwm);
+ ingenic_tcu_counter_stop(tcu_chan);
+}
+
+static int pwm_ingenic_config(struct pwm_chip *chip, struct pwm_device *pwm,
+ int duty_ns, int period_ns)
+{
+ struct ingenic_pwm_chip *our_chip = to_ingenic_pwm_chip(chip);
+ struct ingenic_pwm_channel *our_chan = pwm_get_chip_data(pwm);
+ struct ingenic_tcu_chn *tcu_chan = our_chan->chan;
+ u16 tfull = tcu_chan->full_num, thalf;
+
+ /*
+ * We currently avoid using 64bit arithmetic by using the
+ * fact that anything faster than 1Hz is easily representable
+ * by 32bits.
+ */
+ if (period_ns > NSEC_PER_SEC)
+ return -ERANGE;
+
+ if (period_ns == our_chan->period_ns && duty_ns == our_chan->duty_ns)
+ return 0;
+
+ /* Check to see if we are changing the clock rate of the PWM. */
+ if (our_chan->period_ns != period_ns) {
+ int period;
+
+ period = pwm_ingenic_get_period(our_chip, pwm, period_ns);
+ dev_dbg(our_chip->chip.dev, "duty_ns=%d, period_ns=%d (%u)\n",
+ duty_ns, period_ns, period);
+ if(period < 0)
+ return -ERANGE;
+ tfull = period;
+ }
+ /* Period is too short. */
+ if (tfull <= 1)
+ return -ERANGE;
+
+ /* Note that counters count down. */
+ {
+ unsigned long long tmp;
+ tmp = (unsigned long long)tfull * duty_ns;
+ do_div(tmp, period_ns);
+ thalf = tmp;
+ }
+
+ /* 0% duty is not available */
+ if (!thalf)
+ ++thalf;
+
+ thalf = tfull - thalf;
+ if(!thalf){
+ /* set pwm output 1 */
+ }
+
+ dev_info(our_chip->chip.dev,
+ "thalf=%u/%u\n", thalf, tfull);
+
+ tcu_chan->irq_type = NULL_IRQ_MODE;
+ tcu_chan->is_pwm = 1;
+ tcu_chan->full_num = tfull;
+ tcu_chan->half_num = thalf;
+ tcu_chan->shutdown_mode = 0;
+
+ ingenic_tcu_config(tcu_chan);
+
+ our_chan->period_ns = period_ns;
+ our_chan->duty_ns = duty_ns;
+
+ return 0;
+}
+
+static int pwm_ingenic_set_polarity(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ enum pwm_polarity polarity)
+{
+ struct ingenic_tcu_chn *tcu_chan;
+ bool invert = (polarity == PWM_POLARITY_NORMAL);
+
+ tcu_chan = to_ingenic_tcu_channel(pwm);
+ if (invert) {
+ tcu_chan->init_level = 1;
+ } else {
+ tcu_chan->init_level = 0;
+ }
+
+ return 0;
+}
+
+static const struct pwm_ops pwm_ingenic_ops = {
+ .request = pwm_ingenic_request,
+ .free = pwm_ingenic_free,
+ .enable = pwm_ingenic_enable,
+ .disable = pwm_ingenic_disable,
+ .config = pwm_ingenic_config,
+ .set_polarity = pwm_ingenic_set_polarity,
+ .owner = THIS_MODULE,
+};
+
+#ifdef CONFIG_OF
+/**
+ * of_irq_find_parent - Given a device node, find its interrupt parent node
+ * @child: pointer to device node
+ *
+ * Returns a pointer to the interrupt parent node, or NULL if the interrupt
+ * parent could not be determined.
+ */
+struct device_node *of_pwm_find_parent(struct device_node *child)
+{
+ struct device_node *p;
+ const __be32 *parp;
+
+ if (!of_node_get(child))
+ return NULL;
+
+ do {
+ parp = of_get_property(child, "ingenic,timer-parent", NULL);
+ if (parp == NULL)
+ p = of_get_parent(child);
+ else {
+ p = of_find_node_by_phandle(be32_to_cpup(parp));
+ }
+ of_node_put(child);
+ } while (!p);
+
+ return p;
+}
+
+static void pwm_ingenic_parse_dt(struct ingenic_pwm_chip *chip)
+{
+ struct device_node *np = chip->chip.dev->of_node;
+ struct device_node *child, *parent;
+ int enable_pwm_num = 0;
+
+ for_each_child_of_node(np, child) {
+ const char *status;
+ parent = of_pwm_find_parent(child);
+ of_property_read_string(child, "status", &status);
+
+ if(!strcmp(status, "okay") ){
+ unsigned int info, id;
+ of_property_read_u32(parent, "ingenic,channel-info", &info);
+ id = info & (CHANNEL_BASE_OFF*2 - 1);
+ chip->output_mask |= BIT(id);
+ enable_pwm_num ++;
+ }
+ }
+ chip->chip.of_pwm_n_cells = enable_pwm_num;
+}
+static const struct of_device_id ingenic_pwm_matches[] = {
+ { .compatible = "ingenic,pwm", .data = NULL },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ingenic_pwm_matches);
+#else
+static int pwm_ingenic_parse_dt(struct ingenic_pwm_chip *chip)
+{
+ return -ENODEV;
+}
+#endif
+
+static int pwm_ingenic_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ingenic_pwm_chip *chip;
+ struct device_node *np;
+ unsigned int pwmnum;
+ int ret = 0;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL)
+ return -ENOMEM;
+
+ np = pdev->dev.of_node;
+ pwmnum = of_get_child_count(np);
+ chip->chip.dev = &pdev->dev;
+ chip->chip.dev->of_node = np;
+ chip->chip.ops = &pwm_ingenic_ops;
+ chip->chip.base = -1;
+ chip->chip.npwm = pwmnum;
+
+ if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) {
+ pwm_ingenic_parse_dt(chip);
+ /* chip->chip.of_xlate = of_pwm_xlate_with_flags; */
+ } else {
+ if (!pdev->dev.platform_data) {
+ dev_err(&pdev->dev, "no platform data specified\n");
+ return -EINVAL;
+ }
+ }
+
+ chip->ext_clk = devm_clk_get(&pdev->dev, "ext");
+ chip->rtc_clk = devm_clk_get(&pdev->dev, "rtc");
+ if (IS_ERR(chip->ext_clk) || IS_ERR(chip->rtc_clk)) {
+ dev_err(dev, "failed to get timer base clk\n");
+ return -EINVAL;
+ }
+
+ platform_set_drvdata(pdev, chip);
+
+ ret = pwmchip_add(&chip->chip);
+ if (ret < 0) {
+ dev_err(dev, "failed to register PWM chip\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int pwm_ingenic_remove(struct platform_device *pdev)
+{
+ struct ingenic_pwm_chip *chip = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pwmchip_remove(&chip->chip);
+ if (ret < 0)
+ return ret;
+
+ clk_disable_unprepare(chip->ext_clk);
+ clk_disable_unprepare(chip->rtc_clk);
+
+ return 0;
+}
+
+
+#ifdef CONFIG_PM_SLEEP
+static int pwm_ingenic_suspend(struct device *dev)
+{
+ struct ingenic_pwm_chip *chip = dev_get_drvdata(dev);
+ unsigned int i;
+
+ /*
+ * No one preserves these values during suspend so reset them.
+ * Otherwise driver leaves PWM unconfigured if same values are
+ * passed to pwm_config() next time.
+ */
+ for (i = 0; i < chip->chip.npwm; ++i) {
+ struct pwm_device *pwm = &chip->chip.pwms[i];
+ struct ingenic_pwm_channel *chan = pwm_get_chip_data(pwm);
+ int id;
+ if (!chan)
+ continue;
+
+ id = pwm->hwpwm;
+ chan->chan->disable(id);
+ }
+
+ return 0;
+}
+
+static int pwm_ingenic_resume(struct device *dev)
+{
+ struct ingenic_pwm_chip *chip = dev_get_drvdata(dev);
+ unsigned int chan;
+
+ /*
+ * Inverter setting must be preserved across suspend/resume
+ * as nobody really seems to configure it more than once.
+ */
+ for (chan = 0; chan < chip->chip.npwm; ++chan) {
+ if (chip->output_mask & BIT(chan)) {
+ struct pwm_device *pwm = &chip->chip.pwms[chan];
+ struct ingenic_pwm_channel *chan = pwm_get_chip_data(pwm);
+ int id;
+ if (!chan)
+ continue;
+
+ id = pwm->hwpwm;
+ chan->chan->enable(id);
+ }
+ }
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(pwm_ingenic_pm_ops, pwm_ingenic_suspend,
+ pwm_ingenic_resume);
+
+static struct platform_driver pwm_ingenic_driver = {
+ .driver = {
+ .name = "ingenic,pwm",
+ .pm = &pwm_ingenic_pm_ops,
+ .of_match_table = of_match_ptr(ingenic_pwm_matches),
+ },
+ .probe = pwm_ingenic_probe,
+ .remove = pwm_ingenic_remove,
+};
+
+static int __init ingenic_pwm_init(void)
+{
+ return platform_driver_register(&pwm_ingenic_driver);
+}
+
+static void __exit ingenic_pwm_exit(void)
+{
+ platform_driver_unregister(&pwm_ingenic_driver);
+}
+
+device_initcall_sync(ingenic_pwm_init);
+module_exit(ingenic_pwm_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("bo.liu <bo.liu@ingenic.com>");
+MODULE_ALIAS("platform:ingenic-pwm");