18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* (C) 2015 Pengutronix, Alexander Aring <aar@pengutronix.de> 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Authors: 58c2ecf20Sopenharmony_ci * Alexander Aring <aar@pengutronix.de> 68c2ecf20Sopenharmony_ci * Eric Anholt <eric@anholt.net> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 118c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 128c2ecf20Sopenharmony_ci#include <linux/pm_domain.h> 138c2ecf20Sopenharmony_ci#include <dt-bindings/power/raspberrypi-power.h> 148c2ecf20Sopenharmony_ci#include <soc/bcm2835/raspberrypi-firmware.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci/* 178c2ecf20Sopenharmony_ci * Firmware indices for the old power domains interface. Only a few 188c2ecf20Sopenharmony_ci * of them were actually implemented. 198c2ecf20Sopenharmony_ci */ 208c2ecf20Sopenharmony_ci#define RPI_OLD_POWER_DOMAIN_USB 3 218c2ecf20Sopenharmony_ci#define RPI_OLD_POWER_DOMAIN_V3D 10 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistruct rpi_power_domain { 248c2ecf20Sopenharmony_ci u32 domain; 258c2ecf20Sopenharmony_ci bool enabled; 268c2ecf20Sopenharmony_ci bool old_interface; 278c2ecf20Sopenharmony_ci struct generic_pm_domain base; 288c2ecf20Sopenharmony_ci struct rpi_firmware *fw; 298c2ecf20Sopenharmony_ci}; 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cistruct rpi_power_domains { 328c2ecf20Sopenharmony_ci bool has_new_interface; 338c2ecf20Sopenharmony_ci struct genpd_onecell_data xlate; 348c2ecf20Sopenharmony_ci struct rpi_firmware *fw; 358c2ecf20Sopenharmony_ci struct rpi_power_domain domains[RPI_POWER_DOMAIN_COUNT]; 368c2ecf20Sopenharmony_ci}; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci/* 398c2ecf20Sopenharmony_ci * Packet definition used by RPI_FIRMWARE_SET_POWER_STATE and 408c2ecf20Sopenharmony_ci * RPI_FIRMWARE_SET_DOMAIN_STATE 418c2ecf20Sopenharmony_ci */ 428c2ecf20Sopenharmony_cistruct rpi_power_domain_packet { 438c2ecf20Sopenharmony_ci u32 domain; 448c2ecf20Sopenharmony_ci u32 on; 458c2ecf20Sopenharmony_ci}; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci/* 488c2ecf20Sopenharmony_ci * Asks the firmware to enable or disable power on a specific power 498c2ecf20Sopenharmony_ci * domain. 508c2ecf20Sopenharmony_ci */ 518c2ecf20Sopenharmony_cistatic int rpi_firmware_set_power(struct rpi_power_domain *rpi_domain, bool on) 528c2ecf20Sopenharmony_ci{ 538c2ecf20Sopenharmony_ci struct rpi_power_domain_packet packet; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci packet.domain = rpi_domain->domain; 568c2ecf20Sopenharmony_ci packet.on = on; 578c2ecf20Sopenharmony_ci return rpi_firmware_property(rpi_domain->fw, 588c2ecf20Sopenharmony_ci rpi_domain->old_interface ? 598c2ecf20Sopenharmony_ci RPI_FIRMWARE_SET_POWER_STATE : 608c2ecf20Sopenharmony_ci RPI_FIRMWARE_SET_DOMAIN_STATE, 618c2ecf20Sopenharmony_ci &packet, sizeof(packet)); 628c2ecf20Sopenharmony_ci} 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistatic int rpi_domain_off(struct generic_pm_domain *domain) 658c2ecf20Sopenharmony_ci{ 668c2ecf20Sopenharmony_ci struct rpi_power_domain *rpi_domain = 678c2ecf20Sopenharmony_ci container_of(domain, struct rpi_power_domain, base); 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci return rpi_firmware_set_power(rpi_domain, false); 708c2ecf20Sopenharmony_ci} 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_cistatic int rpi_domain_on(struct generic_pm_domain *domain) 738c2ecf20Sopenharmony_ci{ 748c2ecf20Sopenharmony_ci struct rpi_power_domain *rpi_domain = 758c2ecf20Sopenharmony_ci container_of(domain, struct rpi_power_domain, base); 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci return rpi_firmware_set_power(rpi_domain, true); 788c2ecf20Sopenharmony_ci} 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic void rpi_common_init_power_domain(struct rpi_power_domains *rpi_domains, 818c2ecf20Sopenharmony_ci int xlate_index, const char *name) 828c2ecf20Sopenharmony_ci{ 838c2ecf20Sopenharmony_ci struct rpi_power_domain *dom = &rpi_domains->domains[xlate_index]; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci dom->fw = rpi_domains->fw; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci dom->base.name = name; 888c2ecf20Sopenharmony_ci dom->base.power_on = rpi_domain_on; 898c2ecf20Sopenharmony_ci dom->base.power_off = rpi_domain_off; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci /* 928c2ecf20Sopenharmony_ci * Treat all power domains as off at boot. 938c2ecf20Sopenharmony_ci * 948c2ecf20Sopenharmony_ci * The firmware itself may be keeping some domains on, but 958c2ecf20Sopenharmony_ci * from Linux's perspective all we control is the refcounts 968c2ecf20Sopenharmony_ci * that we give to the firmware, and we can't ask the firmware 978c2ecf20Sopenharmony_ci * to turn off something that we haven't ourselves turned on. 988c2ecf20Sopenharmony_ci */ 998c2ecf20Sopenharmony_ci pm_genpd_init(&dom->base, NULL, true); 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci rpi_domains->xlate.domains[xlate_index] = &dom->base; 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic void rpi_init_power_domain(struct rpi_power_domains *rpi_domains, 1058c2ecf20Sopenharmony_ci int xlate_index, const char *name) 1068c2ecf20Sopenharmony_ci{ 1078c2ecf20Sopenharmony_ci struct rpi_power_domain *dom = &rpi_domains->domains[xlate_index]; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci if (!rpi_domains->has_new_interface) 1108c2ecf20Sopenharmony_ci return; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci /* The DT binding index is the firmware's domain index minus one. */ 1138c2ecf20Sopenharmony_ci dom->domain = xlate_index + 1; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci rpi_common_init_power_domain(rpi_domains, xlate_index, name); 1168c2ecf20Sopenharmony_ci} 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_cistatic void rpi_init_old_power_domain(struct rpi_power_domains *rpi_domains, 1198c2ecf20Sopenharmony_ci int xlate_index, int domain, 1208c2ecf20Sopenharmony_ci const char *name) 1218c2ecf20Sopenharmony_ci{ 1228c2ecf20Sopenharmony_ci struct rpi_power_domain *dom = &rpi_domains->domains[xlate_index]; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci dom->old_interface = true; 1258c2ecf20Sopenharmony_ci dom->domain = domain; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci rpi_common_init_power_domain(rpi_domains, xlate_index, name); 1288c2ecf20Sopenharmony_ci} 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci/* 1318c2ecf20Sopenharmony_ci * Detects whether the firmware supports the new power domains interface. 1328c2ecf20Sopenharmony_ci * 1338c2ecf20Sopenharmony_ci * The firmware doesn't actually return an error on an unknown tag, 1348c2ecf20Sopenharmony_ci * and just skips over it, so we do the detection by putting an 1358c2ecf20Sopenharmony_ci * unexpected value in the return field and checking if it was 1368c2ecf20Sopenharmony_ci * unchanged. 1378c2ecf20Sopenharmony_ci */ 1388c2ecf20Sopenharmony_cistatic bool 1398c2ecf20Sopenharmony_cirpi_has_new_domain_support(struct rpi_power_domains *rpi_domains) 1408c2ecf20Sopenharmony_ci{ 1418c2ecf20Sopenharmony_ci struct rpi_power_domain_packet packet; 1428c2ecf20Sopenharmony_ci int ret; 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci packet.domain = RPI_POWER_DOMAIN_ARM; 1458c2ecf20Sopenharmony_ci packet.on = ~0; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci ret = rpi_firmware_property(rpi_domains->fw, 1488c2ecf20Sopenharmony_ci RPI_FIRMWARE_GET_DOMAIN_STATE, 1498c2ecf20Sopenharmony_ci &packet, sizeof(packet)); 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci return ret == 0 && packet.on != ~0; 1528c2ecf20Sopenharmony_ci} 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_cistatic int rpi_power_probe(struct platform_device *pdev) 1558c2ecf20Sopenharmony_ci{ 1568c2ecf20Sopenharmony_ci struct device_node *fw_np; 1578c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 1588c2ecf20Sopenharmony_ci struct rpi_power_domains *rpi_domains; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci rpi_domains = devm_kzalloc(dev, sizeof(*rpi_domains), GFP_KERNEL); 1618c2ecf20Sopenharmony_ci if (!rpi_domains) 1628c2ecf20Sopenharmony_ci return -ENOMEM; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci rpi_domains->xlate.domains = 1658c2ecf20Sopenharmony_ci devm_kcalloc(dev, 1668c2ecf20Sopenharmony_ci RPI_POWER_DOMAIN_COUNT, 1678c2ecf20Sopenharmony_ci sizeof(*rpi_domains->xlate.domains), 1688c2ecf20Sopenharmony_ci GFP_KERNEL); 1698c2ecf20Sopenharmony_ci if (!rpi_domains->xlate.domains) 1708c2ecf20Sopenharmony_ci return -ENOMEM; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci rpi_domains->xlate.num_domains = RPI_POWER_DOMAIN_COUNT; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci fw_np = of_parse_phandle(pdev->dev.of_node, "firmware", 0); 1758c2ecf20Sopenharmony_ci if (!fw_np) { 1768c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "no firmware node\n"); 1778c2ecf20Sopenharmony_ci return -ENODEV; 1788c2ecf20Sopenharmony_ci } 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci rpi_domains->fw = rpi_firmware_get(fw_np); 1818c2ecf20Sopenharmony_ci of_node_put(fw_np); 1828c2ecf20Sopenharmony_ci if (!rpi_domains->fw) 1838c2ecf20Sopenharmony_ci return -EPROBE_DEFER; 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci rpi_domains->has_new_interface = 1868c2ecf20Sopenharmony_ci rpi_has_new_domain_support(rpi_domains); 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_I2C0, "I2C0"); 1898c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_I2C1, "I2C1"); 1908c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_I2C2, "I2C2"); 1918c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_VIDEO_SCALER, 1928c2ecf20Sopenharmony_ci "VIDEO_SCALER"); 1938c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_VPU1, "VPU1"); 1948c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_HDMI, "HDMI"); 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci /* 1978c2ecf20Sopenharmony_ci * Use the old firmware interface for USB power, so that we 1988c2ecf20Sopenharmony_ci * can turn it on even if the firmware hasn't been updated. 1998c2ecf20Sopenharmony_ci */ 2008c2ecf20Sopenharmony_ci rpi_init_old_power_domain(rpi_domains, RPI_POWER_DOMAIN_USB, 2018c2ecf20Sopenharmony_ci RPI_OLD_POWER_DOMAIN_USB, "USB"); 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_VEC, "VEC"); 2048c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_JPEG, "JPEG"); 2058c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_H264, "H264"); 2068c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_V3D, "V3D"); 2078c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_ISP, "ISP"); 2088c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_UNICAM0, "UNICAM0"); 2098c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_UNICAM1, "UNICAM1"); 2108c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CCP2RX, "CCP2RX"); 2118c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CSI2, "CSI2"); 2128c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CPI, "CPI"); 2138c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_DSI0, "DSI0"); 2148c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_DSI1, "DSI1"); 2158c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_TRANSPOSER, 2168c2ecf20Sopenharmony_ci "TRANSPOSER"); 2178c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CCP2TX, "CCP2TX"); 2188c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CDP, "CDP"); 2198c2ecf20Sopenharmony_ci rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_ARM, "ARM"); 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci of_genpd_add_provider_onecell(dev->of_node, &rpi_domains->xlate); 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, rpi_domains); 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci return 0; 2268c2ecf20Sopenharmony_ci} 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_cistatic const struct of_device_id rpi_power_of_match[] = { 2298c2ecf20Sopenharmony_ci { .compatible = "raspberrypi,bcm2835-power", }, 2308c2ecf20Sopenharmony_ci {}, 2318c2ecf20Sopenharmony_ci}; 2328c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, rpi_power_of_match); 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_cistatic struct platform_driver rpi_power_driver = { 2358c2ecf20Sopenharmony_ci .driver = { 2368c2ecf20Sopenharmony_ci .name = "raspberrypi-power", 2378c2ecf20Sopenharmony_ci .of_match_table = rpi_power_of_match, 2388c2ecf20Sopenharmony_ci }, 2398c2ecf20Sopenharmony_ci .probe = rpi_power_probe, 2408c2ecf20Sopenharmony_ci}; 2418c2ecf20Sopenharmony_cibuiltin_platform_driver(rpi_power_driver); 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ciMODULE_AUTHOR("Alexander Aring <aar@pengutronix.de>"); 2448c2ecf20Sopenharmony_ciMODULE_AUTHOR("Eric Anholt <eric@anholt.net>"); 2458c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Raspberry Pi power domain driver"); 2468c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 247