123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- // 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)
- };
- 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(sizeof(*md), GFP_KERNEL);
- 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", bus->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;
- }
|