| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Defines interfaces for interacting wtih the Raspberry Pi firmware's |
|---|
| 3 | 4 | * property channel. |
|---|
| 4 | 5 | * |
|---|
| 5 | 6 | * Copyright © 2015 Broadcom |
|---|
| 6 | | - * |
|---|
| 7 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 8 | | - * it under the terms of the GNU General Public License version 2 as |
|---|
| 9 | | - * published by the Free Software Foundation. |
|---|
| 10 | 7 | */ |
|---|
| 11 | 8 | |
|---|
| 12 | 9 | #include <linux/dma-mapping.h> |
|---|
| 10 | +#include <linux/kref.h> |
|---|
| 13 | 11 | #include <linux/mailbox_client.h> |
|---|
| 14 | 12 | #include <linux/module.h> |
|---|
| 15 | 13 | #include <linux/of_platform.h> |
|---|
| .. | .. |
|---|
| 23 | 21 | #define MBOX_CHAN_PROPERTY 8 |
|---|
| 24 | 22 | |
|---|
| 25 | 23 | static struct platform_device *rpi_hwmon; |
|---|
| 24 | +static struct platform_device *rpi_clk; |
|---|
| 26 | 25 | |
|---|
| 27 | 26 | struct rpi_firmware { |
|---|
| 28 | 27 | struct mbox_client cl; |
|---|
| 29 | 28 | struct mbox_chan *chan; /* The property channel. */ |
|---|
| 30 | 29 | struct completion c; |
|---|
| 31 | 30 | u32 enabled; |
|---|
| 31 | + |
|---|
| 32 | + struct kref consumers; |
|---|
| 32 | 33 | }; |
|---|
| 33 | 34 | |
|---|
| 34 | 35 | static DEFINE_MUTEX(transaction_lock); |
|---|
| .. | .. |
|---|
| 55 | 56 | reinit_completion(&fw->c); |
|---|
| 56 | 57 | ret = mbox_send_message(fw->chan, &message); |
|---|
| 57 | 58 | if (ret >= 0) { |
|---|
| 58 | | - wait_for_completion(&fw->c); |
|---|
| 59 | | - ret = 0; |
|---|
| 59 | + if (wait_for_completion_timeout(&fw->c, HZ)) { |
|---|
| 60 | + ret = 0; |
|---|
| 61 | + } else { |
|---|
| 62 | + ret = -ETIMEDOUT; |
|---|
| 63 | + WARN_ONCE(1, "Firmware transaction timeout"); |
|---|
| 64 | + } |
|---|
| 60 | 65 | } else { |
|---|
| 61 | 66 | dev_err(fw->cl.dev, "mbox_send_message returned %d\n", ret); |
|---|
| 62 | 67 | } |
|---|
| .. | .. |
|---|
| 175 | 180 | static void |
|---|
| 176 | 181 | rpi_firmware_print_firmware_revision(struct rpi_firmware *fw) |
|---|
| 177 | 182 | { |
|---|
| 183 | + time64_t date_and_time; |
|---|
| 178 | 184 | u32 packet; |
|---|
| 179 | 185 | int ret = rpi_firmware_property(fw, |
|---|
| 180 | 186 | RPI_FIRMWARE_GET_FIRMWARE_REVISION, |
|---|
| 181 | 187 | &packet, sizeof(packet)); |
|---|
| 182 | 188 | |
|---|
| 183 | | - if (ret == 0) { |
|---|
| 184 | | - struct tm tm; |
|---|
| 189 | + if (ret) |
|---|
| 190 | + return; |
|---|
| 185 | 191 | |
|---|
| 186 | | - time64_to_tm(packet, 0, &tm); |
|---|
| 187 | | - |
|---|
| 188 | | - dev_info(fw->cl.dev, |
|---|
| 189 | | - "Attached to firmware from %04ld-%02d-%02d %02d:%02d\n", |
|---|
| 190 | | - tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, |
|---|
| 191 | | - tm.tm_hour, tm.tm_min); |
|---|
| 192 | | - } |
|---|
| 192 | + /* This is not compatible with y2038 */ |
|---|
| 193 | + date_and_time = packet; |
|---|
| 194 | + dev_info(fw->cl.dev, "Attached to firmware from %ptT\n", &date_and_time); |
|---|
| 193 | 195 | } |
|---|
| 194 | 196 | |
|---|
| 195 | 197 | static void |
|---|
| .. | .. |
|---|
| 206 | 208 | -1, NULL, 0); |
|---|
| 207 | 209 | } |
|---|
| 208 | 210 | |
|---|
| 211 | +static void rpi_register_clk_driver(struct device *dev) |
|---|
| 212 | +{ |
|---|
| 213 | + struct device_node *firmware; |
|---|
| 214 | + |
|---|
| 215 | + /* |
|---|
| 216 | + * Earlier DTs don't have a node for the firmware clocks but |
|---|
| 217 | + * rely on us creating a platform device by hand. If we do |
|---|
| 218 | + * have a node for the firmware clocks, just bail out here. |
|---|
| 219 | + */ |
|---|
| 220 | + firmware = of_get_compatible_child(dev->of_node, |
|---|
| 221 | + "raspberrypi,firmware-clocks"); |
|---|
| 222 | + if (firmware) { |
|---|
| 223 | + of_node_put(firmware); |
|---|
| 224 | + return; |
|---|
| 225 | + } |
|---|
| 226 | + |
|---|
| 227 | + rpi_clk = platform_device_register_data(dev, "raspberrypi-clk", |
|---|
| 228 | + -1, NULL, 0); |
|---|
| 229 | +} |
|---|
| 230 | + |
|---|
| 231 | +static void rpi_firmware_delete(struct kref *kref) |
|---|
| 232 | +{ |
|---|
| 233 | + struct rpi_firmware *fw = container_of(kref, struct rpi_firmware, |
|---|
| 234 | + consumers); |
|---|
| 235 | + |
|---|
| 236 | + mbox_free_channel(fw->chan); |
|---|
| 237 | + kfree(fw); |
|---|
| 238 | +} |
|---|
| 239 | + |
|---|
| 240 | +void rpi_firmware_put(struct rpi_firmware *fw) |
|---|
| 241 | +{ |
|---|
| 242 | + kref_put(&fw->consumers, rpi_firmware_delete); |
|---|
| 243 | +} |
|---|
| 244 | +EXPORT_SYMBOL_GPL(rpi_firmware_put); |
|---|
| 245 | + |
|---|
| 246 | +static void devm_rpi_firmware_put(void *data) |
|---|
| 247 | +{ |
|---|
| 248 | + struct rpi_firmware *fw = data; |
|---|
| 249 | + |
|---|
| 250 | + rpi_firmware_put(fw); |
|---|
| 251 | +} |
|---|
| 252 | + |
|---|
| 209 | 253 | static int rpi_firmware_probe(struct platform_device *pdev) |
|---|
| 210 | 254 | { |
|---|
| 211 | 255 | struct device *dev = &pdev->dev; |
|---|
| 212 | 256 | struct rpi_firmware *fw; |
|---|
| 213 | 257 | |
|---|
| 214 | | - fw = devm_kzalloc(dev, sizeof(*fw), GFP_KERNEL); |
|---|
| 258 | + /* |
|---|
| 259 | + * Memory will be freed by rpi_firmware_delete() once all users have |
|---|
| 260 | + * released their firmware handles. Don't use devm_kzalloc() here. |
|---|
| 261 | + */ |
|---|
| 262 | + fw = kzalloc(sizeof(*fw), GFP_KERNEL); |
|---|
| 215 | 263 | if (!fw) |
|---|
| 216 | 264 | return -ENOMEM; |
|---|
| 217 | 265 | |
|---|
| .. | .. |
|---|
| 224 | 272 | int ret = PTR_ERR(fw->chan); |
|---|
| 225 | 273 | if (ret != -EPROBE_DEFER) |
|---|
| 226 | 274 | dev_err(dev, "Failed to get mbox channel: %d\n", ret); |
|---|
| 275 | + kfree(fw); |
|---|
| 227 | 276 | return ret; |
|---|
| 228 | 277 | } |
|---|
| 229 | 278 | |
|---|
| 230 | 279 | init_completion(&fw->c); |
|---|
| 280 | + kref_init(&fw->consumers); |
|---|
| 231 | 281 | |
|---|
| 232 | 282 | platform_set_drvdata(pdev, fw); |
|---|
| 233 | 283 | |
|---|
| 234 | 284 | rpi_firmware_print_firmware_revision(fw); |
|---|
| 235 | 285 | rpi_register_hwmon_driver(dev, fw); |
|---|
| 286 | + rpi_register_clk_driver(dev); |
|---|
| 236 | 287 | |
|---|
| 237 | 288 | return 0; |
|---|
| 289 | +} |
|---|
| 290 | + |
|---|
| 291 | +static void rpi_firmware_shutdown(struct platform_device *pdev) |
|---|
| 292 | +{ |
|---|
| 293 | + struct rpi_firmware *fw = platform_get_drvdata(pdev); |
|---|
| 294 | + |
|---|
| 295 | + if (!fw) |
|---|
| 296 | + return; |
|---|
| 297 | + |
|---|
| 298 | + rpi_firmware_property(fw, RPI_FIRMWARE_NOTIFY_REBOOT, NULL, 0); |
|---|
| 238 | 299 | } |
|---|
| 239 | 300 | |
|---|
| 240 | 301 | static int rpi_firmware_remove(struct platform_device *pdev) |
|---|
| .. | .. |
|---|
| 243 | 304 | |
|---|
| 244 | 305 | platform_device_unregister(rpi_hwmon); |
|---|
| 245 | 306 | rpi_hwmon = NULL; |
|---|
| 246 | | - mbox_free_channel(fw->chan); |
|---|
| 307 | + platform_device_unregister(rpi_clk); |
|---|
| 308 | + rpi_clk = NULL; |
|---|
| 309 | + |
|---|
| 310 | + rpi_firmware_put(fw); |
|---|
| 247 | 311 | |
|---|
| 248 | 312 | return 0; |
|---|
| 249 | 313 | } |
|---|
| .. | .. |
|---|
| 252 | 316 | * rpi_firmware_get - Get pointer to rpi_firmware structure. |
|---|
| 253 | 317 | * @firmware_node: Pointer to the firmware Device Tree node. |
|---|
| 254 | 318 | * |
|---|
| 319 | + * The reference to rpi_firmware has to be released with rpi_firmware_put(). |
|---|
| 320 | + * |
|---|
| 255 | 321 | * Returns NULL is the firmware device is not ready. |
|---|
| 256 | 322 | */ |
|---|
| 257 | 323 | struct rpi_firmware *rpi_firmware_get(struct device_node *firmware_node) |
|---|
| 258 | 324 | { |
|---|
| 259 | 325 | struct platform_device *pdev = of_find_device_by_node(firmware_node); |
|---|
| 326 | + struct rpi_firmware *fw; |
|---|
| 260 | 327 | |
|---|
| 261 | 328 | if (!pdev) |
|---|
| 262 | 329 | return NULL; |
|---|
| 263 | 330 | |
|---|
| 264 | | - return platform_get_drvdata(pdev); |
|---|
| 331 | + fw = platform_get_drvdata(pdev); |
|---|
| 332 | + if (!fw) |
|---|
| 333 | + goto err_put_device; |
|---|
| 334 | + |
|---|
| 335 | + if (!kref_get_unless_zero(&fw->consumers)) |
|---|
| 336 | + goto err_put_device; |
|---|
| 337 | + |
|---|
| 338 | + put_device(&pdev->dev); |
|---|
| 339 | + |
|---|
| 340 | + return fw; |
|---|
| 341 | + |
|---|
| 342 | +err_put_device: |
|---|
| 343 | + put_device(&pdev->dev); |
|---|
| 344 | + return NULL; |
|---|
| 265 | 345 | } |
|---|
| 266 | 346 | EXPORT_SYMBOL_GPL(rpi_firmware_get); |
|---|
| 347 | + |
|---|
| 348 | +/** |
|---|
| 349 | + * devm_rpi_firmware_get - Get pointer to rpi_firmware structure. |
|---|
| 350 | + * @firmware_node: Pointer to the firmware Device Tree node. |
|---|
| 351 | + * |
|---|
| 352 | + * Returns NULL is the firmware device is not ready. |
|---|
| 353 | + */ |
|---|
| 354 | +struct rpi_firmware *devm_rpi_firmware_get(struct device *dev, |
|---|
| 355 | + struct device_node *firmware_node) |
|---|
| 356 | +{ |
|---|
| 357 | + struct rpi_firmware *fw; |
|---|
| 358 | + |
|---|
| 359 | + fw = rpi_firmware_get(firmware_node); |
|---|
| 360 | + if (!fw) |
|---|
| 361 | + return NULL; |
|---|
| 362 | + |
|---|
| 363 | + if (devm_add_action_or_reset(dev, devm_rpi_firmware_put, fw)) |
|---|
| 364 | + return NULL; |
|---|
| 365 | + |
|---|
| 366 | + return fw; |
|---|
| 367 | +} |
|---|
| 368 | +EXPORT_SYMBOL_GPL(devm_rpi_firmware_get); |
|---|
| 267 | 369 | |
|---|
| 268 | 370 | static const struct of_device_id rpi_firmware_of_match[] = { |
|---|
| 269 | 371 | { .compatible = "raspberrypi,bcm2835-firmware", }, |
|---|
| .. | .. |
|---|
| 277 | 379 | .of_match_table = rpi_firmware_of_match, |
|---|
| 278 | 380 | }, |
|---|
| 279 | 381 | .probe = rpi_firmware_probe, |
|---|
| 382 | + .shutdown = rpi_firmware_shutdown, |
|---|
| 280 | 383 | .remove = rpi_firmware_remove, |
|---|
| 281 | 384 | }; |
|---|
| 282 | 385 | module_platform_driver(rpi_firmware_driver); |
|---|