188 lines · 4.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only// Copyright(c) 2019-2020 Intel Corporation. #include <linux/device.h>#include <linux/acpi.h>#include <linux/pm_runtime.h>#include <linux/soundwire/sdw.h>#include <linux/soundwire/sdw_type.h>#include "bus.h" /* * The 3s value for autosuspend will only be used if there are no * devices physically attached on a bus segment. In practice enabling * the bus operation will result in children devices become active and * the master device will only suspend when all its children are no * longer active. */#define SDW_MASTER_SUSPEND_DELAY_MS 3000 /* * The sysfs for properties reflects the MIPI description as given * in the MIPI DisCo spec * * Base file is: *	sdw-master-N *      |---- revision *      |---- clk_stop_modes *      |---- max_clk_freq *      |---- clk_freq *      |---- clk_gears *      |---- default_row *      |---- default_col *      |---- dynamic_shape *      |---- err_threshold */ #define sdw_master_attr(field, format_string)				\static ssize_t field##_show(struct device *dev,				\			    struct device_attribute *attr,		\			    char *buf)					\{									\	struct sdw_master_device *md = dev_to_sdw_master_device(dev);	\	return sprintf(buf, format_string, md->bus->prop.field);	\}									\static DEVICE_ATTR_RO(field) sdw_master_attr(revision, "0x%x\n");sdw_master_attr(clk_stop_modes, "0x%x\n");sdw_master_attr(max_clk_freq, "%d\n");sdw_master_attr(default_row, "%d\n");sdw_master_attr(default_col, "%d\n");sdw_master_attr(default_frame_rate, "%d\n");sdw_master_attr(dynamic_frame, "%d\n");sdw_master_attr(err_threshold, "%d\n"); static ssize_t clock_frequencies_show(struct device *dev,				      struct device_attribute *attr, char *buf){	struct sdw_master_device *md = dev_to_sdw_master_device(dev);	ssize_t size = 0;	int i; 	for (i = 0; i < md->bus->prop.num_clk_freq; i++)		size += sprintf(buf + size, "%8d ",				md->bus->prop.clk_freq[i]);	size += sprintf(buf + size, "\n"); 	return size;}static DEVICE_ATTR_RO(clock_frequencies); static ssize_t clock_gears_show(struct device *dev,				struct device_attribute *attr, char *buf){	struct sdw_master_device *md = dev_to_sdw_master_device(dev);	ssize_t size = 0;	int i; 	for (i = 0; i < md->bus->prop.num_clk_gears; i++)		size += sprintf(buf + size, "%8d ",				md->bus->prop.clk_gears[i]);	size += sprintf(buf + size, "\n"); 	return size;}static DEVICE_ATTR_RO(clock_gears); static struct attribute *master_node_attrs[] = {	&dev_attr_revision.attr,	&dev_attr_clk_stop_modes.attr,	&dev_attr_max_clk_freq.attr,	&dev_attr_default_row.attr,	&dev_attr_default_col.attr,	&dev_attr_default_frame_rate.attr,	&dev_attr_dynamic_frame.attr,	&dev_attr_err_threshold.attr,	&dev_attr_clock_frequencies.attr,	&dev_attr_clock_gears.attr,	NULL,};ATTRIBUTE_GROUPS(master_node); static void sdw_master_device_release(struct device *dev){	struct sdw_master_device *md = dev_to_sdw_master_device(dev); 	kfree(md);} static const struct dev_pm_ops master_dev_pm = {	SET_RUNTIME_PM_OPS(pm_generic_runtime_suspend,			   pm_generic_runtime_resume, NULL)}; const struct device_type sdw_master_type = {	.name =		"soundwire_master",	.release =	sdw_master_device_release,	.pm = &master_dev_pm,}; /** * sdw_master_device_add() - create a Linux Master Device representation. * @bus: SDW bus instance * @parent: parent device * @fwnode: firmware node handle */int sdw_master_device_add(struct sdw_bus *bus, struct device *parent,			  struct fwnode_handle *fwnode){	struct sdw_master_device *md;	int ret; 	if (!parent)		return -EINVAL; 	md = kzalloc_obj(*md);	if (!md)		return -ENOMEM; 	md->dev.bus = &sdw_bus_type;	md->dev.type = &sdw_master_type;	md->dev.parent = parent;	md->dev.groups = master_node_groups;	md->dev.of_node = parent->of_node;	md->dev.fwnode = fwnode;	md->dev.dma_mask = parent->dma_mask; 	dev_set_name(&md->dev, "sdw-master-%d-%d", bus->controller_id, bus->link_id); 	ret = device_register(&md->dev);	if (ret) {		dev_err(parent, "Failed to add master: ret %d\n", ret);		/*		 * On err, don't free but drop ref as this will be freed		 * when release method is invoked.		 */		put_device(&md->dev);		goto device_register_err;	} 	/* add shortcuts to improve code readability/compactness */	md->bus = bus;	bus->dev = &md->dev;	bus->md = md; 	pm_runtime_set_autosuspend_delay(&bus->md->dev, SDW_MASTER_SUSPEND_DELAY_MS);	pm_runtime_use_autosuspend(&bus->md->dev);	pm_runtime_mark_last_busy(&bus->md->dev);	pm_runtime_set_active(&bus->md->dev);	pm_runtime_enable(&bus->md->dev);	pm_runtime_idle(&bus->md->dev);device_register_err:	return ret;} /** * sdw_master_device_del() - delete a Linux Master Device representation. * @bus: bus handle * * This function is the dual of sdw_master_device_add() */int sdw_master_device_del(struct sdw_bus *bus){	pm_runtime_disable(&bus->md->dev);	device_unregister(bus->dev); 	return 0;}