18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci// Copyright(c) 2019-2020 Intel Corporation. 38c2ecf20Sopenharmony_ci 48c2ecf20Sopenharmony_ci#include <linux/device.h> 58c2ecf20Sopenharmony_ci#include <linux/acpi.h> 68c2ecf20Sopenharmony_ci#include <linux/pm_runtime.h> 78c2ecf20Sopenharmony_ci#include <linux/soundwire/sdw.h> 88c2ecf20Sopenharmony_ci#include <linux/soundwire/sdw_type.h> 98c2ecf20Sopenharmony_ci#include "bus.h" 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci/* 128c2ecf20Sopenharmony_ci * The 3s value for autosuspend will only be used if there are no 138c2ecf20Sopenharmony_ci * devices physically attached on a bus segment. In practice enabling 148c2ecf20Sopenharmony_ci * the bus operation will result in children devices become active and 158c2ecf20Sopenharmony_ci * the master device will only suspend when all its children are no 168c2ecf20Sopenharmony_ci * longer active. 178c2ecf20Sopenharmony_ci */ 188c2ecf20Sopenharmony_ci#define SDW_MASTER_SUSPEND_DELAY_MS 3000 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci/* 218c2ecf20Sopenharmony_ci * The sysfs for properties reflects the MIPI description as given 228c2ecf20Sopenharmony_ci * in the MIPI DisCo spec 238c2ecf20Sopenharmony_ci * 248c2ecf20Sopenharmony_ci * Base file is: 258c2ecf20Sopenharmony_ci * sdw-master-N 268c2ecf20Sopenharmony_ci * |---- revision 278c2ecf20Sopenharmony_ci * |---- clk_stop_modes 288c2ecf20Sopenharmony_ci * |---- max_clk_freq 298c2ecf20Sopenharmony_ci * |---- clk_freq 308c2ecf20Sopenharmony_ci * |---- clk_gears 318c2ecf20Sopenharmony_ci * |---- default_row 328c2ecf20Sopenharmony_ci * |---- default_col 338c2ecf20Sopenharmony_ci * |---- dynamic_shape 348c2ecf20Sopenharmony_ci * |---- err_threshold 358c2ecf20Sopenharmony_ci */ 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci#define sdw_master_attr(field, format_string) \ 388c2ecf20Sopenharmony_cistatic ssize_t field##_show(struct device *dev, \ 398c2ecf20Sopenharmony_ci struct device_attribute *attr, \ 408c2ecf20Sopenharmony_ci char *buf) \ 418c2ecf20Sopenharmony_ci{ \ 428c2ecf20Sopenharmony_ci struct sdw_master_device *md = dev_to_sdw_master_device(dev); \ 438c2ecf20Sopenharmony_ci return sprintf(buf, format_string, md->bus->prop.field); \ 448c2ecf20Sopenharmony_ci} \ 458c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(field) 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cisdw_master_attr(revision, "0x%x\n"); 488c2ecf20Sopenharmony_cisdw_master_attr(clk_stop_modes, "0x%x\n"); 498c2ecf20Sopenharmony_cisdw_master_attr(max_clk_freq, "%d\n"); 508c2ecf20Sopenharmony_cisdw_master_attr(default_row, "%d\n"); 518c2ecf20Sopenharmony_cisdw_master_attr(default_col, "%d\n"); 528c2ecf20Sopenharmony_cisdw_master_attr(default_frame_rate, "%d\n"); 538c2ecf20Sopenharmony_cisdw_master_attr(dynamic_frame, "%d\n"); 548c2ecf20Sopenharmony_cisdw_master_attr(err_threshold, "%d\n"); 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_cistatic ssize_t clock_frequencies_show(struct device *dev, 578c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci struct sdw_master_device *md = dev_to_sdw_master_device(dev); 608c2ecf20Sopenharmony_ci ssize_t size = 0; 618c2ecf20Sopenharmony_ci int i; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci for (i = 0; i < md->bus->prop.num_clk_freq; i++) 648c2ecf20Sopenharmony_ci size += sprintf(buf + size, "%8d ", 658c2ecf20Sopenharmony_ci md->bus->prop.clk_freq[i]); 668c2ecf20Sopenharmony_ci size += sprintf(buf + size, "\n"); 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci return size; 698c2ecf20Sopenharmony_ci} 708c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(clock_frequencies); 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_cistatic ssize_t clock_gears_show(struct device *dev, 738c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci struct sdw_master_device *md = dev_to_sdw_master_device(dev); 768c2ecf20Sopenharmony_ci ssize_t size = 0; 778c2ecf20Sopenharmony_ci int i; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci for (i = 0; i < md->bus->prop.num_clk_gears; i++) 808c2ecf20Sopenharmony_ci size += sprintf(buf + size, "%8d ", 818c2ecf20Sopenharmony_ci md->bus->prop.clk_gears[i]); 828c2ecf20Sopenharmony_ci size += sprintf(buf + size, "\n"); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci return size; 858c2ecf20Sopenharmony_ci} 868c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(clock_gears); 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_cistatic struct attribute *master_node_attrs[] = { 898c2ecf20Sopenharmony_ci &dev_attr_revision.attr, 908c2ecf20Sopenharmony_ci &dev_attr_clk_stop_modes.attr, 918c2ecf20Sopenharmony_ci &dev_attr_max_clk_freq.attr, 928c2ecf20Sopenharmony_ci &dev_attr_default_row.attr, 938c2ecf20Sopenharmony_ci &dev_attr_default_col.attr, 948c2ecf20Sopenharmony_ci &dev_attr_default_frame_rate.attr, 958c2ecf20Sopenharmony_ci &dev_attr_dynamic_frame.attr, 968c2ecf20Sopenharmony_ci &dev_attr_err_threshold.attr, 978c2ecf20Sopenharmony_ci &dev_attr_clock_frequencies.attr, 988c2ecf20Sopenharmony_ci &dev_attr_clock_gears.attr, 998c2ecf20Sopenharmony_ci NULL, 1008c2ecf20Sopenharmony_ci}; 1018c2ecf20Sopenharmony_ciATTRIBUTE_GROUPS(master_node); 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_cistatic void sdw_master_device_release(struct device *dev) 1048c2ecf20Sopenharmony_ci{ 1058c2ecf20Sopenharmony_ci struct sdw_master_device *md = dev_to_sdw_master_device(dev); 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci kfree(md); 1088c2ecf20Sopenharmony_ci} 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic const struct dev_pm_ops master_dev_pm = { 1118c2ecf20Sopenharmony_ci SET_RUNTIME_PM_OPS(pm_generic_runtime_suspend, 1128c2ecf20Sopenharmony_ci pm_generic_runtime_resume, NULL) 1138c2ecf20Sopenharmony_ci}; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_cistruct device_type sdw_master_type = { 1168c2ecf20Sopenharmony_ci .name = "soundwire_master", 1178c2ecf20Sopenharmony_ci .release = sdw_master_device_release, 1188c2ecf20Sopenharmony_ci .pm = &master_dev_pm, 1198c2ecf20Sopenharmony_ci}; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci/** 1228c2ecf20Sopenharmony_ci * sdw_master_device_add() - create a Linux Master Device representation. 1238c2ecf20Sopenharmony_ci * @bus: SDW bus instance 1248c2ecf20Sopenharmony_ci * @parent: parent device 1258c2ecf20Sopenharmony_ci * @fwnode: firmware node handle 1268c2ecf20Sopenharmony_ci */ 1278c2ecf20Sopenharmony_ciint sdw_master_device_add(struct sdw_bus *bus, struct device *parent, 1288c2ecf20Sopenharmony_ci struct fwnode_handle *fwnode) 1298c2ecf20Sopenharmony_ci{ 1308c2ecf20Sopenharmony_ci struct sdw_master_device *md; 1318c2ecf20Sopenharmony_ci int ret; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci if (!parent) 1348c2ecf20Sopenharmony_ci return -EINVAL; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci md = kzalloc(sizeof(*md), GFP_KERNEL); 1378c2ecf20Sopenharmony_ci if (!md) 1388c2ecf20Sopenharmony_ci return -ENOMEM; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci md->dev.bus = &sdw_bus_type; 1418c2ecf20Sopenharmony_ci md->dev.type = &sdw_master_type; 1428c2ecf20Sopenharmony_ci md->dev.parent = parent; 1438c2ecf20Sopenharmony_ci md->dev.groups = master_node_groups; 1448c2ecf20Sopenharmony_ci md->dev.of_node = parent->of_node; 1458c2ecf20Sopenharmony_ci md->dev.fwnode = fwnode; 1468c2ecf20Sopenharmony_ci md->dev.dma_mask = parent->dma_mask; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci dev_set_name(&md->dev, "sdw-master-%d", bus->id); 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci ret = device_register(&md->dev); 1518c2ecf20Sopenharmony_ci if (ret) { 1528c2ecf20Sopenharmony_ci dev_err(parent, "Failed to add master: ret %d\n", ret); 1538c2ecf20Sopenharmony_ci /* 1548c2ecf20Sopenharmony_ci * On err, don't free but drop ref as this will be freed 1558c2ecf20Sopenharmony_ci * when release method is invoked. 1568c2ecf20Sopenharmony_ci */ 1578c2ecf20Sopenharmony_ci put_device(&md->dev); 1588c2ecf20Sopenharmony_ci goto device_register_err; 1598c2ecf20Sopenharmony_ci } 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci /* add shortcuts to improve code readability/compactness */ 1628c2ecf20Sopenharmony_ci md->bus = bus; 1638c2ecf20Sopenharmony_ci bus->dev = &md->dev; 1648c2ecf20Sopenharmony_ci bus->md = md; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci pm_runtime_set_autosuspend_delay(&bus->md->dev, SDW_MASTER_SUSPEND_DELAY_MS); 1678c2ecf20Sopenharmony_ci pm_runtime_use_autosuspend(&bus->md->dev); 1688c2ecf20Sopenharmony_ci pm_runtime_mark_last_busy(&bus->md->dev); 1698c2ecf20Sopenharmony_ci pm_runtime_set_active(&bus->md->dev); 1708c2ecf20Sopenharmony_ci pm_runtime_enable(&bus->md->dev); 1718c2ecf20Sopenharmony_ci pm_runtime_idle(&bus->md->dev); 1728c2ecf20Sopenharmony_cidevice_register_err: 1738c2ecf20Sopenharmony_ci return ret; 1748c2ecf20Sopenharmony_ci} 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci/** 1778c2ecf20Sopenharmony_ci * sdw_master_device_del() - delete a Linux Master Device representation. 1788c2ecf20Sopenharmony_ci * @bus: bus handle 1798c2ecf20Sopenharmony_ci * 1808c2ecf20Sopenharmony_ci * This function is the dual of sdw_master_device_add() 1818c2ecf20Sopenharmony_ci */ 1828c2ecf20Sopenharmony_ciint sdw_master_device_del(struct sdw_bus *bus) 1838c2ecf20Sopenharmony_ci{ 1848c2ecf20Sopenharmony_ci pm_runtime_disable(&bus->md->dev); 1858c2ecf20Sopenharmony_ci device_unregister(bus->dev); 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci return 0; 1888c2ecf20Sopenharmony_ci} 189