summaryrefslogtreecommitdiff
path: root/drivers/firmware/smccc/bus.c
blob: 0357a7c48af0ba9e0a75ef12b9d607314e71a6a5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2026 Arm Limited
 */

#include <linux/arm-smccc-bus.h>
#include <linux/slab.h>

static int arm_smccc_bus_match(struct device *dev,
		const struct device_driver *drv)
{
	const struct arm_smccc_device_id *id_table;
	const struct arm_smccc_device *smccc_dev = to_arm_smccc_device(dev);

	id_table = to_arm_smccc_driver(drv)->id_table;
	if (!id_table)
		return 0;

	while (id_table->func_id) {
		if (smccc_dev->func_id == id_table->func_id)
			return 1;
		id_table++;
	}

	return 0;
}

static int arm_smccc_bus_probe(struct device *dev)
{
	struct arm_smccc_driver *smccc_drv = to_arm_smccc_driver(dev->driver);

	return smccc_drv->probe(to_arm_smccc_device(dev));
}

static void arm_smccc_bus_remove(struct device *dev)
{
	struct arm_smccc_driver *smcc_drv = to_arm_smccc_driver(dev->driver);

	if (smcc_drv->remove)
		smcc_drv->remove(to_arm_smccc_device(dev));
}

static int arm_smccc_bus_uevent(const struct device *dev,
		struct kobj_uevent_env *env)
{
	const struct arm_smccc_device *smccc_dev = to_arm_smccc_device(dev);

	return add_uevent_var(env, "MODALIAS=" ARM_SMCCC_MODULE_PREFIX "f%08X",
			      smccc_dev->func_id);
}

static ssize_t modalias_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct arm_smccc_device *smccc_dev = to_arm_smccc_device(dev);

	return sysfs_emit(buf, ARM_SMCCC_MODULE_PREFIX "f%08X\n",
			  smccc_dev->func_id);
}
static DEVICE_ATTR_RO(modalias);

static struct attribute *arm_smccc_device_attrs[] = {
	&dev_attr_modalias.attr,
	NULL,
};
ATTRIBUTE_GROUPS(arm_smccc_device);

const struct bus_type arm_smccc_bus_type = {
	.name = "arm_smccc",
	.match = arm_smccc_bus_match,
	.probe = arm_smccc_bus_probe,
	.remove = arm_smccc_bus_remove,
	.uevent = arm_smccc_bus_uevent,
	.dev_groups = arm_smccc_device_groups,
};
EXPORT_SYMBOL_GPL(arm_smccc_bus_type);

int arm_smccc_driver_register(struct arm_smccc_driver *driver,
		struct module *owner, const char *mod_name)
{
	if (!driver->probe || !driver->id_table)
		return -EINVAL;

	driver->driver.bus = &arm_smccc_bus_type;
	driver->driver.name = driver->name;
	driver->driver.owner = owner;
	driver->driver.mod_name = mod_name;

	return driver_register(&driver->driver);
}
EXPORT_SYMBOL_GPL(arm_smccc_driver_register);

void arm_smccc_driver_unregister(struct arm_smccc_driver *driver)
{
	driver_unregister(&driver->driver);
}
EXPORT_SYMBOL_GPL(arm_smccc_driver_unregister);

static void arm_smccc_release_device(struct device *dev)
{
	struct arm_smccc_device *smccc_dev = to_arm_smccc_device(dev);

	kfree(smccc_dev);
}

static struct device *arm_smccc_device_alloc(u32 func_id)
{
	struct arm_smccc_device *smccc_dev;

	smccc_dev = kzalloc_obj(*smccc_dev);
	if (!smccc_dev)
		return NULL;

	smccc_dev->func_id = func_id;
	smccc_dev->dev.bus = &arm_smccc_bus_type;
	smccc_dev->dev.release = arm_smccc_release_device;
	device_initialize(&smccc_dev->dev);

	return &smccc_dev->dev;
}

struct arm_smccc_device *arm_smccc_device_register(const char *name, u32 func_id)
{
	int ret;

	if (!name)
		return ERR_PTR(-EINVAL);

	struct device *dev __free(put_device) = arm_smccc_device_alloc(func_id);
	if (!dev)
		return ERR_PTR(-ENOMEM);

	ret = dev_set_name(dev, "%s", name);
	if (ret)
		return ERR_PTR(ret);

	ret = device_add(dev);
	if (ret)
		return ERR_PTR(ret);

	return to_arm_smccc_device(no_free_ptr(dev));
}
EXPORT_SYMBOL_GPL(arm_smccc_device_register);

void arm_smccc_device_unregister(struct arm_smccc_device *smccc_dev)
{
	if (!smccc_dev)
		return;

	device_unregister(&smccc_dev->dev);
}
EXPORT_SYMBOL_GPL(arm_smccc_device_unregister);

static int __init arm_smccc_bus_init(void)
{
	return bus_register(&arm_smccc_bus_type);
}
subsys_initcall(arm_smccc_bus_init);