Commit | Line | Data |
---|---|---|
7ceaa40b PLB |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // Copyright(c) 2019-2020 Intel Corporation. | |
3 | ||
4 | #include <linux/device.h> | |
5 | #include <linux/acpi.h> | |
26d97022 | 6 | #include <linux/pm_runtime.h> |
7ceaa40b PLB |
7 | #include <linux/soundwire/sdw.h> |
8 | #include <linux/soundwire/sdw_type.h> | |
9 | #include "bus.h" | |
10 | ||
c5778ca4 PLB |
11 | /* |
12 | * The sysfs for properties reflects the MIPI description as given | |
13 | * in the MIPI DisCo spec | |
14 | * | |
15 | * Base file is: | |
16 | * sdw-master-N | |
17 | * |---- revision | |
18 | * |---- clk_stop_modes | |
19 | * |---- max_clk_freq | |
20 | * |---- clk_freq | |
21 | * |---- clk_gears | |
22 | * |---- default_row | |
23 | * |---- default_col | |
24 | * |---- dynamic_shape | |
25 | * |---- err_threshold | |
26 | */ | |
27 | ||
28 | #define sdw_master_attr(field, format_string) \ | |
29 | static ssize_t field##_show(struct device *dev, \ | |
30 | struct device_attribute *attr, \ | |
31 | char *buf) \ | |
32 | { \ | |
33 | struct sdw_master_device *md = dev_to_sdw_master_device(dev); \ | |
34 | return sprintf(buf, format_string, md->bus->prop.field); \ | |
35 | } \ | |
36 | static DEVICE_ATTR_RO(field) | |
37 | ||
38 | sdw_master_attr(revision, "0x%x\n"); | |
39 | sdw_master_attr(clk_stop_modes, "0x%x\n"); | |
40 | sdw_master_attr(max_clk_freq, "%d\n"); | |
41 | sdw_master_attr(default_row, "%d\n"); | |
42 | sdw_master_attr(default_col, "%d\n"); | |
43 | sdw_master_attr(default_frame_rate, "%d\n"); | |
44 | sdw_master_attr(dynamic_frame, "%d\n"); | |
45 | sdw_master_attr(err_threshold, "%d\n"); | |
46 | ||
47 | static ssize_t clock_frequencies_show(struct device *dev, | |
48 | struct device_attribute *attr, char *buf) | |
49 | { | |
50 | struct sdw_master_device *md = dev_to_sdw_master_device(dev); | |
51 | ssize_t size = 0; | |
52 | int i; | |
53 | ||
54 | for (i = 0; i < md->bus->prop.num_clk_freq; i++) | |
55 | size += sprintf(buf + size, "%8d ", | |
56 | md->bus->prop.clk_freq[i]); | |
57 | size += sprintf(buf + size, "\n"); | |
58 | ||
59 | return size; | |
60 | } | |
61 | static DEVICE_ATTR_RO(clock_frequencies); | |
62 | ||
63 | static ssize_t clock_gears_show(struct device *dev, | |
64 | struct device_attribute *attr, char *buf) | |
65 | { | |
66 | struct sdw_master_device *md = dev_to_sdw_master_device(dev); | |
67 | ssize_t size = 0; | |
68 | int i; | |
69 | ||
70 | for (i = 0; i < md->bus->prop.num_clk_gears; i++) | |
71 | size += sprintf(buf + size, "%8d ", | |
72 | md->bus->prop.clk_gears[i]); | |
73 | size += sprintf(buf + size, "\n"); | |
74 | ||
75 | return size; | |
76 | } | |
77 | static DEVICE_ATTR_RO(clock_gears); | |
78 | ||
79 | static struct attribute *master_node_attrs[] = { | |
80 | &dev_attr_revision.attr, | |
81 | &dev_attr_clk_stop_modes.attr, | |
82 | &dev_attr_max_clk_freq.attr, | |
83 | &dev_attr_default_row.attr, | |
84 | &dev_attr_default_col.attr, | |
85 | &dev_attr_default_frame_rate.attr, | |
86 | &dev_attr_dynamic_frame.attr, | |
87 | &dev_attr_err_threshold.attr, | |
88 | &dev_attr_clock_frequencies.attr, | |
89 | &dev_attr_clock_gears.attr, | |
90 | NULL, | |
91 | }; | |
92 | ATTRIBUTE_GROUPS(master_node); | |
93 | ||
7ceaa40b PLB |
94 | static void sdw_master_device_release(struct device *dev) |
95 | { | |
96 | struct sdw_master_device *md = dev_to_sdw_master_device(dev); | |
97 | ||
98 | kfree(md); | |
99 | } | |
100 | ||
26d97022 BL |
101 | static const struct dev_pm_ops master_dev_pm = { |
102 | SET_RUNTIME_PM_OPS(pm_generic_runtime_suspend, | |
103 | pm_generic_runtime_resume, NULL) | |
104 | }; | |
105 | ||
7ceaa40b PLB |
106 | struct device_type sdw_master_type = { |
107 | .name = "soundwire_master", | |
108 | .release = sdw_master_device_release, | |
26d97022 | 109 | .pm = &master_dev_pm, |
7ceaa40b PLB |
110 | }; |
111 | ||
112 | /** | |
113 | * sdw_master_device_add() - create a Linux Master Device representation. | |
114 | * @bus: SDW bus instance | |
115 | * @parent: parent device | |
116 | * @fwnode: firmware node handle | |
117 | */ | |
118 | int sdw_master_device_add(struct sdw_bus *bus, struct device *parent, | |
119 | struct fwnode_handle *fwnode) | |
120 | { | |
121 | struct sdw_master_device *md; | |
122 | int ret; | |
123 | ||
124 | if (!parent) | |
125 | return -EINVAL; | |
126 | ||
127 | md = kzalloc(sizeof(*md), GFP_KERNEL); | |
128 | if (!md) | |
129 | return -ENOMEM; | |
130 | ||
131 | md->dev.bus = &sdw_bus_type; | |
132 | md->dev.type = &sdw_master_type; | |
133 | md->dev.parent = parent; | |
c5778ca4 | 134 | md->dev.groups = master_node_groups; |
7ceaa40b PLB |
135 | md->dev.of_node = parent->of_node; |
136 | md->dev.fwnode = fwnode; | |
137 | md->dev.dma_mask = parent->dma_mask; | |
138 | ||
139 | dev_set_name(&md->dev, "sdw-master-%d", bus->id); | |
140 | ||
141 | ret = device_register(&md->dev); | |
142 | if (ret) { | |
143 | dev_err(parent, "Failed to add master: ret %d\n", ret); | |
144 | /* | |
145 | * On err, don't free but drop ref as this will be freed | |
146 | * when release method is invoked. | |
147 | */ | |
148 | put_device(&md->dev); | |
149 | goto device_register_err; | |
150 | } | |
151 | ||
152 | /* add shortcuts to improve code readability/compactness */ | |
153 | md->bus = bus; | |
154 | bus->dev = &md->dev; | |
155 | bus->md = md; | |
156 | ||
bd84256e | 157 | pm_runtime_enable(&bus->md->dev); |
7ceaa40b PLB |
158 | device_register_err: |
159 | return ret; | |
160 | } | |
161 | ||
162 | /** | |
163 | * sdw_master_device_del() - delete a Linux Master Device representation. | |
164 | * @bus: bus handle | |
165 | * | |
166 | * This function is the dual of sdw_master_device_add() | |
167 | */ | |
168 | int sdw_master_device_del(struct sdw_bus *bus) | |
169 | { | |
bd84256e | 170 | pm_runtime_disable(&bus->md->dev); |
7ceaa40b PLB |
171 | device_unregister(bus->dev); |
172 | ||
173 | return 0; | |
174 | } |