1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Copyright (c) 2017-2018 Vasily Khoruzhick <anarsoul@gmail.com> 4 */ 5 6 #include <common.h> 7 #include <div64.h> 8 #include <dm.h> 9 #include <pwm.h> 10 #include <regmap.h> 11 #include <syscon.h> 12 #include <asm/io.h> 13 #include <asm/arch/pwm.h> 14 #include <asm/arch/gpio.h> 15 #include <power/regulator.h> 16 17 DECLARE_GLOBAL_DATA_PTR; 18 19 #define OSC_24MHZ 24000000 20 21 struct sunxi_pwm_priv { 22 struct sunxi_pwm *regs; 23 bool invert; 24 u32 prescaler; 25 }; 26 27 static const u32 prescaler_table[] = { 28 120, /* 0000 */ 29 180, /* 0001 */ 30 240, /* 0010 */ 31 360, /* 0011 */ 32 480, /* 0100 */ 33 0, /* 0101 */ 34 0, /* 0110 */ 35 0, /* 0111 */ 36 12000, /* 1000 */ 37 24000, /* 1001 */ 38 36000, /* 1010 */ 39 48000, /* 1011 */ 40 72000, /* 1100 */ 41 0, /* 1101 */ 42 0, /* 1110 */ 43 1, /* 1111 */ 44 }; 45 46 static int sunxi_pwm_config_pinmux(void) 47 { 48 #ifdef CONFIG_MACH_SUN50I 49 sunxi_gpio_set_cfgpin(SUNXI_GPD(22), SUNXI_GPD_PWM); 50 #endif 51 return 0; 52 } 53 54 static int sunxi_pwm_set_invert(struct udevice *dev, uint channel, 55 bool polarity) 56 { 57 struct sunxi_pwm_priv *priv = dev_get_priv(dev); 58 59 debug("%s: polarity=%u\n", __func__, polarity); 60 priv->invert = polarity; 61 62 return 0; 63 } 64 65 static int sunxi_pwm_set_config(struct udevice *dev, uint channel, 66 uint period_ns, uint duty_ns) 67 { 68 struct sunxi_pwm_priv *priv = dev_get_priv(dev); 69 struct sunxi_pwm *regs = priv->regs; 70 int prescaler; 71 u32 v, period = 0, duty; 72 u64 scaled_freq = 0; 73 const u32 nsecs_per_sec = 1000000000U; 74 75 debug("%s: period_ns=%u, duty_ns=%u\n", __func__, period_ns, duty_ns); 76 77 for (prescaler = 0; prescaler < SUNXI_PWM_CTRL_PRESCALE0_MASK; 78 prescaler++) { 79 if (!prescaler_table[prescaler]) 80 continue; 81 scaled_freq = lldiv(OSC_24MHZ, prescaler_table[prescaler]); 82 period = lldiv(scaled_freq * period_ns, nsecs_per_sec); 83 if (period - 1 <= SUNXI_PWM_CH0_PERIOD_MAX) 84 break; 85 } 86 87 if (period - 1 > SUNXI_PWM_CH0_PERIOD_MAX) { 88 debug("%s: failed to find prescaler value\n", __func__); 89 return -EINVAL; 90 } 91 92 duty = lldiv(scaled_freq * duty_ns, nsecs_per_sec); 93 94 if (priv->prescaler != prescaler) { 95 /* Mask clock to update prescaler */ 96 v = readl(®s->ctrl); 97 v &= ~SUNXI_PWM_CTRL_CLK_GATE; 98 writel(v, ®s->ctrl); 99 v &= ~SUNXI_PWM_CTRL_PRESCALE0_MASK; 100 v |= (priv->prescaler & SUNXI_PWM_CTRL_PRESCALE0_MASK); 101 writel(v, ®s->ctrl); 102 v |= SUNXI_PWM_CTRL_CLK_GATE; 103 writel(v, ®s->ctrl); 104 priv->prescaler = prescaler; 105 } 106 107 writel(SUNXI_PWM_CH0_PERIOD_PRD(period) | 108 SUNXI_PWM_CH0_PERIOD_DUTY(duty), ®s->ch0_period); 109 110 debug("%s: prescaler: %d, period: %d, duty: %d\n", 111 __func__, priv->prescaler, 112 period, duty); 113 114 return 0; 115 } 116 117 static int sunxi_pwm_set_enable(struct udevice *dev, uint channel, bool enable) 118 { 119 struct sunxi_pwm_priv *priv = dev_get_priv(dev); 120 struct sunxi_pwm *regs = priv->regs; 121 u32 v; 122 123 debug("%s: Enable '%s'\n", __func__, dev->name); 124 125 v = readl(®s->ctrl); 126 if (!enable) { 127 v &= ~SUNXI_PWM_CTRL_ENABLE0; 128 writel(v, ®s->ctrl); 129 return 0; 130 } 131 132 sunxi_pwm_config_pinmux(); 133 134 if (priv->invert) 135 v &= ~SUNXI_PWM_CTRL_CH0_ACT_STA; 136 else 137 v |= SUNXI_PWM_CTRL_CH0_ACT_STA; 138 v |= SUNXI_PWM_CTRL_ENABLE0; 139 writel(v, ®s->ctrl); 140 141 return 0; 142 } 143 144 static int sunxi_pwm_ofdata_to_platdata(struct udevice *dev) 145 { 146 struct sunxi_pwm_priv *priv = dev_get_priv(dev); 147 148 priv->regs = (struct sunxi_pwm *)devfdt_get_addr(dev); 149 150 return 0; 151 } 152 153 static int sunxi_pwm_probe(struct udevice *dev) 154 { 155 return 0; 156 } 157 158 static const struct pwm_ops sunxi_pwm_ops = { 159 .set_invert = sunxi_pwm_set_invert, 160 .set_config = sunxi_pwm_set_config, 161 .set_enable = sunxi_pwm_set_enable, 162 }; 163 164 static const struct udevice_id sunxi_pwm_ids[] = { 165 { .compatible = "allwinner,sun5i-a13-pwm" }, 166 { .compatible = "allwinner,sun50i-a64-pwm" }, 167 { } 168 }; 169 170 U_BOOT_DRIVER(sunxi_pwm) = { 171 .name = "sunxi_pwm", 172 .id = UCLASS_PWM, 173 .of_match = sunxi_pwm_ids, 174 .ops = &sunxi_pwm_ops, 175 .ofdata_to_platdata = sunxi_pwm_ofdata_to_platdata, 176 .probe = sunxi_pwm_probe, 177 .priv_auto_alloc_size = sizeof(struct sunxi_pwm_priv), 178 }; 179