1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright 2021 Nicolas Saenz Julienne <nsaenzjulienne@suse.de> |
4 | * For more information on Raspberry Pi's PoE hat see: |
5 | * https://www.raspberrypi.org/products/poe-hat/ |
6 | * |
7 | * Limitations: |
8 | * - No disable bit, so a disabled PWM is simulated by duty_cycle 0 |
9 | * - Only normal polarity |
10 | * - Fixed 12.5 kHz period |
11 | * |
12 | * The current period is completed when HW is reconfigured. |
13 | */ |
14 | |
15 | #include <linux/module.h> |
16 | #include <linux/of.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/pwm.h> |
19 | |
20 | #include <soc/bcm2835/raspberrypi-firmware.h> |
21 | #include <dt-bindings/pwm/raspberrypi,firmware-poe-pwm.h> |
22 | |
23 | #define RPI_PWM_MAX_DUTY 255 |
24 | #define RPI_PWM_PERIOD_NS 80000 /* 12.5 kHz */ |
25 | |
26 | #define RPI_PWM_CUR_DUTY_REG 0x0 |
27 | |
28 | struct raspberrypi_pwm { |
29 | struct rpi_firmware *firmware; |
30 | unsigned int duty_cycle; |
31 | }; |
32 | |
33 | struct raspberrypi_pwm_prop { |
34 | __le32 reg; |
35 | __le32 val; |
36 | __le32 ret; |
37 | } __packed; |
38 | |
39 | static inline |
40 | struct raspberrypi_pwm *raspberrypi_pwm_from_chip(struct pwm_chip *chip) |
41 | { |
42 | return pwmchip_get_drvdata(chip); |
43 | } |
44 | |
45 | static int raspberrypi_pwm_set_property(struct rpi_firmware *firmware, |
46 | u32 reg, u32 val) |
47 | { |
48 | struct raspberrypi_pwm_prop msg = { |
49 | .reg = cpu_to_le32(reg), |
50 | .val = cpu_to_le32(val), |
51 | }; |
52 | int ret; |
53 | |
54 | ret = rpi_firmware_property(fw: firmware, tag: RPI_FIRMWARE_SET_POE_HAT_VAL, |
55 | data: &msg, len: sizeof(msg)); |
56 | if (ret) |
57 | return ret; |
58 | if (msg.ret) |
59 | return -EIO; |
60 | |
61 | return 0; |
62 | } |
63 | |
64 | static int raspberrypi_pwm_get_property(struct rpi_firmware *firmware, |
65 | u32 reg, u32 *val) |
66 | { |
67 | struct raspberrypi_pwm_prop msg = { |
68 | .reg = cpu_to_le32(reg), |
69 | }; |
70 | int ret; |
71 | |
72 | ret = rpi_firmware_property(fw: firmware, tag: RPI_FIRMWARE_GET_POE_HAT_VAL, |
73 | data: &msg, len: sizeof(msg)); |
74 | if (ret) |
75 | return ret; |
76 | if (msg.ret) |
77 | return -EIO; |
78 | |
79 | *val = le32_to_cpu(msg.val); |
80 | |
81 | return 0; |
82 | } |
83 | |
84 | static int raspberrypi_pwm_get_state(struct pwm_chip *chip, |
85 | struct pwm_device *pwm, |
86 | struct pwm_state *state) |
87 | { |
88 | struct raspberrypi_pwm *rpipwm = raspberrypi_pwm_from_chip(chip); |
89 | |
90 | state->period = RPI_PWM_PERIOD_NS; |
91 | state->duty_cycle = DIV_ROUND_UP(rpipwm->duty_cycle * RPI_PWM_PERIOD_NS, |
92 | RPI_PWM_MAX_DUTY); |
93 | state->enabled = !!(rpipwm->duty_cycle); |
94 | state->polarity = PWM_POLARITY_NORMAL; |
95 | |
96 | return 0; |
97 | } |
98 | |
99 | static int raspberrypi_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, |
100 | const struct pwm_state *state) |
101 | { |
102 | struct raspberrypi_pwm *rpipwm = raspberrypi_pwm_from_chip(chip); |
103 | unsigned int duty_cycle; |
104 | int ret; |
105 | |
106 | if (state->period < RPI_PWM_PERIOD_NS || |
107 | state->polarity != PWM_POLARITY_NORMAL) |
108 | return -EINVAL; |
109 | |
110 | if (!state->enabled) |
111 | duty_cycle = 0; |
112 | else if (state->duty_cycle < RPI_PWM_PERIOD_NS) |
113 | duty_cycle = DIV_ROUND_DOWN_ULL(state->duty_cycle * RPI_PWM_MAX_DUTY, |
114 | RPI_PWM_PERIOD_NS); |
115 | else |
116 | duty_cycle = RPI_PWM_MAX_DUTY; |
117 | |
118 | if (duty_cycle == rpipwm->duty_cycle) |
119 | return 0; |
120 | |
121 | ret = raspberrypi_pwm_set_property(firmware: rpipwm->firmware, RPI_PWM_CUR_DUTY_REG, |
122 | val: duty_cycle); |
123 | if (ret) { |
124 | dev_err(pwmchip_parent(chip), "Failed to set duty cycle: %pe\n" , |
125 | ERR_PTR(ret)); |
126 | return ret; |
127 | } |
128 | |
129 | rpipwm->duty_cycle = duty_cycle; |
130 | |
131 | return 0; |
132 | } |
133 | |
134 | static const struct pwm_ops raspberrypi_pwm_ops = { |
135 | .get_state = raspberrypi_pwm_get_state, |
136 | .apply = raspberrypi_pwm_apply, |
137 | }; |
138 | |
139 | static int raspberrypi_pwm_probe(struct platform_device *pdev) |
140 | { |
141 | struct device_node *firmware_node; |
142 | struct device *dev = &pdev->dev; |
143 | struct rpi_firmware *firmware; |
144 | struct pwm_chip *chip; |
145 | struct raspberrypi_pwm *rpipwm; |
146 | int ret; |
147 | |
148 | firmware_node = of_get_parent(node: dev->of_node); |
149 | if (!firmware_node) { |
150 | dev_err(dev, "Missing firmware node\n" ); |
151 | return -ENOENT; |
152 | } |
153 | |
154 | firmware = devm_rpi_firmware_get(dev: &pdev->dev, firmware_node); |
155 | of_node_put(node: firmware_node); |
156 | if (!firmware) |
157 | return dev_err_probe(dev, err: -EPROBE_DEFER, |
158 | fmt: "Failed to get firmware handle\n" ); |
159 | |
160 | chip = devm_pwmchip_alloc(parent: &pdev->dev, RASPBERRYPI_FIRMWARE_PWM_NUM, |
161 | sizeof_priv: sizeof(*rpipwm)); |
162 | if (IS_ERR(ptr: chip)) |
163 | return PTR_ERR(ptr: chip); |
164 | rpipwm = raspberrypi_pwm_from_chip(chip); |
165 | |
166 | rpipwm->firmware = firmware; |
167 | chip->ops = &raspberrypi_pwm_ops; |
168 | |
169 | ret = raspberrypi_pwm_get_property(firmware: rpipwm->firmware, RPI_PWM_CUR_DUTY_REG, |
170 | val: &rpipwm->duty_cycle); |
171 | if (ret) { |
172 | dev_err(dev, "Failed to get duty cycle: %pe\n" , ERR_PTR(ret)); |
173 | return ret; |
174 | } |
175 | |
176 | return devm_pwmchip_add(dev, chip); |
177 | } |
178 | |
179 | static const struct of_device_id raspberrypi_pwm_of_match[] = { |
180 | { .compatible = "raspberrypi,firmware-poe-pwm" , }, |
181 | { } |
182 | }; |
183 | MODULE_DEVICE_TABLE(of, raspberrypi_pwm_of_match); |
184 | |
185 | static struct platform_driver raspberrypi_pwm_driver = { |
186 | .driver = { |
187 | .name = "raspberrypi-poe-pwm" , |
188 | .of_match_table = raspberrypi_pwm_of_match, |
189 | }, |
190 | .probe = raspberrypi_pwm_probe, |
191 | }; |
192 | module_platform_driver(raspberrypi_pwm_driver); |
193 | |
194 | MODULE_AUTHOR("Nicolas Saenz Julienne <nsaenzjulienne@suse.de>" ); |
195 | MODULE_DESCRIPTION("Raspberry Pi Firmware Based PWM Bus Driver" ); |
196 | MODULE_LICENSE("GPL v2" ); |
197 | |