0001
0002
0003
0004
0005
0006
0007
0008 #include <linux/device.h>
0009 #include <linux/property.h>
0010 #include <linux/slab.h>
0011
0012 #include <linux/surface_aggregator/controller.h>
0013 #include <linux/surface_aggregator/device.h>
0014
0015 #include "bus.h"
0016 #include "controller.h"
0017
0018
0019
0020
0021 static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
0022 char *buf)
0023 {
0024 struct ssam_device *sdev = to_ssam_device(dev);
0025
0026 return sysfs_emit(buf, "ssam:d%02Xc%02Xt%02Xi%02Xf%02X\n",
0027 sdev->uid.domain, sdev->uid.category, sdev->uid.target,
0028 sdev->uid.instance, sdev->uid.function);
0029 }
0030 static DEVICE_ATTR_RO(modalias);
0031
0032 static struct attribute *ssam_device_attrs[] = {
0033 &dev_attr_modalias.attr,
0034 NULL,
0035 };
0036 ATTRIBUTE_GROUPS(ssam_device);
0037
0038 static int ssam_device_uevent(struct device *dev, struct kobj_uevent_env *env)
0039 {
0040 struct ssam_device *sdev = to_ssam_device(dev);
0041
0042 return add_uevent_var(env, "MODALIAS=ssam:d%02Xc%02Xt%02Xi%02Xf%02X",
0043 sdev->uid.domain, sdev->uid.category,
0044 sdev->uid.target, sdev->uid.instance,
0045 sdev->uid.function);
0046 }
0047
0048 static void ssam_device_release(struct device *dev)
0049 {
0050 struct ssam_device *sdev = to_ssam_device(dev);
0051
0052 ssam_controller_put(sdev->ctrl);
0053 fwnode_handle_put(sdev->dev.fwnode);
0054 kfree(sdev);
0055 }
0056
0057 const struct device_type ssam_device_type = {
0058 .name = "surface_aggregator_device",
0059 .groups = ssam_device_groups,
0060 .uevent = ssam_device_uevent,
0061 .release = ssam_device_release,
0062 };
0063 EXPORT_SYMBOL_GPL(ssam_device_type);
0064
0065
0066
0067
0068
0069
0070
0071
0072
0073
0074
0075
0076
0077
0078 struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl,
0079 struct ssam_device_uid uid)
0080 {
0081 struct ssam_device *sdev;
0082
0083 sdev = kzalloc(sizeof(*sdev), GFP_KERNEL);
0084 if (!sdev)
0085 return NULL;
0086
0087 device_initialize(&sdev->dev);
0088 sdev->dev.bus = &ssam_bus_type;
0089 sdev->dev.type = &ssam_device_type;
0090 sdev->dev.parent = ssam_controller_device(ctrl);
0091 sdev->ctrl = ssam_controller_get(ctrl);
0092 sdev->uid = uid;
0093
0094 dev_set_name(&sdev->dev, "%02x:%02x:%02x:%02x:%02x",
0095 sdev->uid.domain, sdev->uid.category, sdev->uid.target,
0096 sdev->uid.instance, sdev->uid.function);
0097
0098 return sdev;
0099 }
0100 EXPORT_SYMBOL_GPL(ssam_device_alloc);
0101
0102
0103
0104
0105
0106
0107
0108
0109
0110
0111
0112
0113
0114
0115
0116
0117
0118
0119
0120
0121
0122
0123
0124
0125
0126
0127 int ssam_device_add(struct ssam_device *sdev)
0128 {
0129 int status;
0130
0131
0132
0133
0134
0135
0136
0137
0138
0139
0140
0141
0142
0143
0144
0145
0146
0147
0148 ssam_controller_statelock(sdev->ctrl);
0149
0150 if (sdev->ctrl->state != SSAM_CONTROLLER_STARTED) {
0151 ssam_controller_stateunlock(sdev->ctrl);
0152 return -ENODEV;
0153 }
0154
0155 status = device_add(&sdev->dev);
0156
0157 ssam_controller_stateunlock(sdev->ctrl);
0158 return status;
0159 }
0160 EXPORT_SYMBOL_GPL(ssam_device_add);
0161
0162
0163
0164
0165
0166
0167
0168 void ssam_device_remove(struct ssam_device *sdev)
0169 {
0170 device_unregister(&sdev->dev);
0171 }
0172 EXPORT_SYMBOL_GPL(ssam_device_remove);
0173
0174
0175
0176
0177
0178
0179
0180
0181
0182
0183
0184
0185
0186 static bool ssam_device_id_compatible(const struct ssam_device_id *id,
0187 struct ssam_device_uid uid)
0188 {
0189 if (id->domain != uid.domain || id->category != uid.category)
0190 return false;
0191
0192 if ((id->match_flags & SSAM_MATCH_TARGET) && id->target != uid.target)
0193 return false;
0194
0195 if ((id->match_flags & SSAM_MATCH_INSTANCE) && id->instance != uid.instance)
0196 return false;
0197
0198 if ((id->match_flags & SSAM_MATCH_FUNCTION) && id->function != uid.function)
0199 return false;
0200
0201 return true;
0202 }
0203
0204
0205
0206
0207
0208
0209
0210
0211
0212
0213
0214 static bool ssam_device_id_is_null(const struct ssam_device_id *id)
0215 {
0216 return id->match_flags == 0 &&
0217 id->domain == 0 &&
0218 id->category == 0 &&
0219 id->target == 0 &&
0220 id->instance == 0 &&
0221 id->function == 0 &&
0222 id->driver_data == 0;
0223 }
0224
0225
0226
0227
0228
0229
0230
0231
0232
0233 const struct ssam_device_id *ssam_device_id_match(const struct ssam_device_id *table,
0234 const struct ssam_device_uid uid)
0235 {
0236 const struct ssam_device_id *id;
0237
0238 for (id = table; !ssam_device_id_is_null(id); ++id)
0239 if (ssam_device_id_compatible(id, uid))
0240 return id;
0241
0242 return NULL;
0243 }
0244 EXPORT_SYMBOL_GPL(ssam_device_id_match);
0245
0246
0247
0248
0249
0250
0251
0252
0253
0254
0255
0256
0257
0258
0259
0260
0261
0262 const struct ssam_device_id *ssam_device_get_match(const struct ssam_device *dev)
0263 {
0264 const struct ssam_device_driver *sdrv;
0265
0266 sdrv = to_ssam_device_driver(dev->dev.driver);
0267 if (!sdrv)
0268 return NULL;
0269
0270 if (!sdrv->match_table)
0271 return NULL;
0272
0273 return ssam_device_id_match(sdrv->match_table, dev->uid);
0274 }
0275 EXPORT_SYMBOL_GPL(ssam_device_get_match);
0276
0277
0278
0279
0280
0281
0282
0283
0284
0285
0286
0287
0288
0289
0290
0291
0292
0293
0294
0295 const void *ssam_device_get_match_data(const struct ssam_device *dev)
0296 {
0297 const struct ssam_device_id *id;
0298
0299 id = ssam_device_get_match(dev);
0300 if (!id)
0301 return NULL;
0302
0303 return (const void *)id->driver_data;
0304 }
0305 EXPORT_SYMBOL_GPL(ssam_device_get_match_data);
0306
0307 static int ssam_bus_match(struct device *dev, struct device_driver *drv)
0308 {
0309 struct ssam_device_driver *sdrv = to_ssam_device_driver(drv);
0310 struct ssam_device *sdev = to_ssam_device(dev);
0311
0312 if (!is_ssam_device(dev))
0313 return 0;
0314
0315 return !!ssam_device_id_match(sdrv->match_table, sdev->uid);
0316 }
0317
0318 static int ssam_bus_probe(struct device *dev)
0319 {
0320 return to_ssam_device_driver(dev->driver)
0321 ->probe(to_ssam_device(dev));
0322 }
0323
0324 static void ssam_bus_remove(struct device *dev)
0325 {
0326 struct ssam_device_driver *sdrv = to_ssam_device_driver(dev->driver);
0327
0328 if (sdrv->remove)
0329 sdrv->remove(to_ssam_device(dev));
0330 }
0331
0332 struct bus_type ssam_bus_type = {
0333 .name = "surface_aggregator",
0334 .match = ssam_bus_match,
0335 .probe = ssam_bus_probe,
0336 .remove = ssam_bus_remove,
0337 };
0338 EXPORT_SYMBOL_GPL(ssam_bus_type);
0339
0340
0341
0342
0343
0344
0345
0346
0347
0348 int __ssam_device_driver_register(struct ssam_device_driver *sdrv,
0349 struct module *owner)
0350 {
0351 sdrv->driver.owner = owner;
0352 sdrv->driver.bus = &ssam_bus_type;
0353
0354
0355 sdrv->driver.probe_type = PROBE_PREFER_ASYNCHRONOUS;
0356
0357 return driver_register(&sdrv->driver);
0358 }
0359 EXPORT_SYMBOL_GPL(__ssam_device_driver_register);
0360
0361
0362
0363
0364
0365 void ssam_device_driver_unregister(struct ssam_device_driver *sdrv)
0366 {
0367 driver_unregister(&sdrv->driver);
0368 }
0369 EXPORT_SYMBOL_GPL(ssam_device_driver_unregister);
0370
0371
0372
0373
0374
0375
0376
0377 int ssam_bus_register(void)
0378 {
0379 return bus_register(&ssam_bus_type);
0380 }
0381
0382
0383
0384
0385 void ssam_bus_unregister(void)
0386 {
0387 return bus_unregister(&ssam_bus_type);
0388 }
0389
0390
0391
0392
0393 static int ssam_device_uid_from_string(const char *str, struct ssam_device_uid *uid)
0394 {
0395 u8 d, tc, tid, iid, fn;
0396 int n;
0397
0398 n = sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn);
0399 if (n != 5)
0400 return -EINVAL;
0401
0402 uid->domain = d;
0403 uid->category = tc;
0404 uid->target = tid;
0405 uid->instance = iid;
0406 uid->function = fn;
0407
0408 return 0;
0409 }
0410
0411 static int ssam_get_uid_for_node(struct fwnode_handle *node, struct ssam_device_uid *uid)
0412 {
0413 const char *str = fwnode_get_name(node);
0414
0415
0416
0417
0418
0419 if (strncmp(str, "ssam:", strlen("ssam:")) != 0)
0420 return -ENODEV;
0421
0422 str += strlen("ssam:");
0423 return ssam_device_uid_from_string(str, uid);
0424 }
0425
0426 static int ssam_add_client_device(struct device *parent, struct ssam_controller *ctrl,
0427 struct fwnode_handle *node)
0428 {
0429 struct ssam_device_uid uid;
0430 struct ssam_device *sdev;
0431 int status;
0432
0433 status = ssam_get_uid_for_node(node, &uid);
0434 if (status)
0435 return status;
0436
0437 sdev = ssam_device_alloc(ctrl, uid);
0438 if (!sdev)
0439 return -ENOMEM;
0440
0441 sdev->dev.parent = parent;
0442 sdev->dev.fwnode = fwnode_handle_get(node);
0443
0444 status = ssam_device_add(sdev);
0445 if (status)
0446 ssam_device_put(sdev);
0447
0448 return status;
0449 }
0450
0451
0452
0453
0454
0455
0456
0457
0458
0459
0460
0461
0462
0463
0464
0465
0466
0467
0468
0469
0470
0471
0472
0473
0474
0475 int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl,
0476 struct fwnode_handle *node)
0477 {
0478 struct fwnode_handle *child;
0479 int status;
0480
0481 fwnode_for_each_child_node(node, child) {
0482
0483
0484
0485
0486
0487 status = ssam_add_client_device(parent, ctrl, child);
0488 if (status && status != -ENODEV)
0489 goto err;
0490 }
0491
0492 return 0;
0493 err:
0494 ssam_remove_clients(parent);
0495 return status;
0496 }
0497 EXPORT_SYMBOL_GPL(__ssam_register_clients);
0498
0499 static int ssam_remove_device(struct device *dev, void *_data)
0500 {
0501 struct ssam_device *sdev = to_ssam_device(dev);
0502
0503 if (is_ssam_device(dev))
0504 ssam_device_remove(sdev);
0505
0506 return 0;
0507 }
0508
0509
0510
0511
0512
0513
0514
0515
0516
0517
0518 void ssam_remove_clients(struct device *dev)
0519 {
0520 device_for_each_child_reverse(dev, NULL, ssam_remove_device);
0521 }
0522 EXPORT_SYMBOL_GPL(ssam_remove_clients);