1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds * driver.c - device id matching, driver model, etc.
41da177e4SLinus Torvalds *
51da177e4SLinus Torvalds * Copyright 2002 Adam Belay <ambx1@neo.rr.com>
61da177e4SLinus Torvalds */
71da177e4SLinus Torvalds
81da177e4SLinus Torvalds #include <linux/string.h>
91da177e4SLinus Torvalds #include <linux/list.h>
101da177e4SLinus Torvalds #include <linux/module.h>
111da177e4SLinus Torvalds #include <linux/ctype.h>
121da177e4SLinus Torvalds #include <linux/slab.h>
131da177e4SLinus Torvalds #include <linux/pnp.h>
141da177e4SLinus Torvalds #include "base.h"
151da177e4SLinus Torvalds
compare_func(const char * ida,const char * idb)161da177e4SLinus Torvalds static int compare_func(const char *ida, const char *idb)
171da177e4SLinus Torvalds {
181da177e4SLinus Torvalds int i;
1907d4e9afSBjorn Helgaas
201da177e4SLinus Torvalds /* we only need to compare the last 4 chars */
219dd78466SBjorn Helgaas for (i = 3; i < 7; i++) {
221da177e4SLinus Torvalds if (ida[i] != 'X' &&
239dd78466SBjorn Helgaas idb[i] != 'X' && toupper(ida[i]) != toupper(idb[i]))
241da177e4SLinus Torvalds return 0;
251da177e4SLinus Torvalds }
261da177e4SLinus Torvalds return 1;
271da177e4SLinus Torvalds }
281da177e4SLinus Torvalds
compare_pnp_id(struct pnp_id * pos,const char * id)291da177e4SLinus Torvalds int compare_pnp_id(struct pnp_id *pos, const char *id)
301da177e4SLinus Torvalds {
311da177e4SLinus Torvalds if (!pos || !id || (strlen(id) != 7))
321da177e4SLinus Torvalds return 0;
331da177e4SLinus Torvalds if (memcmp(id, "ANYDEVS", 7) == 0)
341da177e4SLinus Torvalds return 1;
351da177e4SLinus Torvalds while (pos) {
361da177e4SLinus Torvalds if (memcmp(pos->id, id, 3) == 0)
371da177e4SLinus Torvalds if (compare_func(pos->id, id) == 1)
381da177e4SLinus Torvalds return 1;
391da177e4SLinus Torvalds pos = pos->next;
401da177e4SLinus Torvalds }
411da177e4SLinus Torvalds return 0;
421da177e4SLinus Torvalds }
431da177e4SLinus Torvalds
match_device(struct pnp_driver * drv,struct pnp_dev * dev)449dd78466SBjorn Helgaas static const struct pnp_device_id *match_device(struct pnp_driver *drv,
459dd78466SBjorn Helgaas struct pnp_dev *dev)
461da177e4SLinus Torvalds {
471da177e4SLinus Torvalds const struct pnp_device_id *drv_id = drv->id_table;
4807d4e9afSBjorn Helgaas
491da177e4SLinus Torvalds if (!drv_id)
501da177e4SLinus Torvalds return NULL;
511da177e4SLinus Torvalds
521da177e4SLinus Torvalds while (*drv_id->id) {
531da177e4SLinus Torvalds if (compare_pnp_id(dev->id, drv_id->id))
541da177e4SLinus Torvalds return drv_id;
551da177e4SLinus Torvalds drv_id++;
561da177e4SLinus Torvalds }
571da177e4SLinus Torvalds return NULL;
581da177e4SLinus Torvalds }
591da177e4SLinus Torvalds
pnp_device_attach(struct pnp_dev * pnp_dev)601da177e4SLinus Torvalds int pnp_device_attach(struct pnp_dev *pnp_dev)
611da177e4SLinus Torvalds {
6238f6b38dSRafael J. Wysocki mutex_lock(&pnp_lock);
631da177e4SLinus Torvalds if (pnp_dev->status != PNP_READY) {
6438f6b38dSRafael J. Wysocki mutex_unlock(&pnp_lock);
651da177e4SLinus Torvalds return -EBUSY;
661da177e4SLinus Torvalds }
671da177e4SLinus Torvalds pnp_dev->status = PNP_ATTACHED;
6838f6b38dSRafael J. Wysocki mutex_unlock(&pnp_lock);
691da177e4SLinus Torvalds return 0;
701da177e4SLinus Torvalds }
71d02908adSJinchao Wang EXPORT_SYMBOL(pnp_device_attach);
721da177e4SLinus Torvalds
pnp_device_detach(struct pnp_dev * pnp_dev)731da177e4SLinus Torvalds void pnp_device_detach(struct pnp_dev *pnp_dev)
741da177e4SLinus Torvalds {
7538f6b38dSRafael J. Wysocki mutex_lock(&pnp_lock);
761da177e4SLinus Torvalds if (pnp_dev->status == PNP_ATTACHED)
771da177e4SLinus Torvalds pnp_dev->status = PNP_READY;
7838f6b38dSRafael J. Wysocki mutex_unlock(&pnp_lock);
791da177e4SLinus Torvalds }
80d02908adSJinchao Wang EXPORT_SYMBOL(pnp_device_detach);
811da177e4SLinus Torvalds
pnp_device_probe(struct device * dev)821da177e4SLinus Torvalds static int pnp_device_probe(struct device *dev)
831da177e4SLinus Torvalds {
841da177e4SLinus Torvalds int error;
851da177e4SLinus Torvalds struct pnp_driver *pnp_drv;
861da177e4SLinus Torvalds struct pnp_dev *pnp_dev;
871da177e4SLinus Torvalds const struct pnp_device_id *dev_id = NULL;
881da177e4SLinus Torvalds pnp_dev = to_pnp_dev(dev);
891da177e4SLinus Torvalds pnp_drv = to_pnp_driver(dev->driver);
901da177e4SLinus Torvalds
911da177e4SLinus Torvalds error = pnp_device_attach(pnp_dev);
921da177e4SLinus Torvalds if (error < 0)
931da177e4SLinus Torvalds return error;
941da177e4SLinus Torvalds
951da177e4SLinus Torvalds if (pnp_dev->active == 0) {
961da177e4SLinus Torvalds if (!(pnp_drv->flags & PNP_DRIVER_RES_DO_NOT_CHANGE)) {
971da177e4SLinus Torvalds error = pnp_activate_dev(pnp_dev);
981da177e4SLinus Torvalds if (error < 0)
991da177e4SLinus Torvalds return error;
1001da177e4SLinus Torvalds }
1011da177e4SLinus Torvalds } else if ((pnp_drv->flags & PNP_DRIVER_RES_DISABLE)
1021da177e4SLinus Torvalds == PNP_DRIVER_RES_DISABLE) {
1031da177e4SLinus Torvalds error = pnp_disable_dev(pnp_dev);
1041da177e4SLinus Torvalds if (error < 0)
1051da177e4SLinus Torvalds return error;
1061da177e4SLinus Torvalds }
1071da177e4SLinus Torvalds error = 0;
1081da177e4SLinus Torvalds if (pnp_drv->probe) {
1091da177e4SLinus Torvalds dev_id = match_device(pnp_drv, pnp_dev);
1101da177e4SLinus Torvalds if (dev_id != NULL)
1111da177e4SLinus Torvalds error = pnp_drv->probe(pnp_dev, dev_id);
1121da177e4SLinus Torvalds }
1131da177e4SLinus Torvalds if (error >= 0) {
1141da177e4SLinus Torvalds pnp_dev->driver = pnp_drv;
1151da177e4SLinus Torvalds error = 0;
1161da177e4SLinus Torvalds } else
1171da177e4SLinus Torvalds goto fail;
118a05d0781SBjorn Helgaas
1191da177e4SLinus Torvalds return error;
1201da177e4SLinus Torvalds
1211da177e4SLinus Torvalds fail:
1221da177e4SLinus Torvalds pnp_device_detach(pnp_dev);
1231da177e4SLinus Torvalds return error;
1241da177e4SLinus Torvalds }
1251da177e4SLinus Torvalds
pnp_device_remove(struct device * dev)126fc7a6209SUwe Kleine-König static void pnp_device_remove(struct device *dev)
1271da177e4SLinus Torvalds {
1281da177e4SLinus Torvalds struct pnp_dev *pnp_dev = to_pnp_dev(dev);
1291da177e4SLinus Torvalds struct pnp_driver *drv = pnp_dev->driver;
1301da177e4SLinus Torvalds
1311da177e4SLinus Torvalds if (drv) {
1321da177e4SLinus Torvalds if (drv->remove)
1331da177e4SLinus Torvalds drv->remove(pnp_dev);
1341da177e4SLinus Torvalds pnp_dev->driver = NULL;
1351da177e4SLinus Torvalds }
136e0f03e87SHeiner Kallweit
137e0f03e87SHeiner Kallweit if (pnp_dev->active &&
138e0f03e87SHeiner Kallweit (!drv || !(drv->flags & PNP_DRIVER_RES_DO_NOT_CHANGE)))
139e0f03e87SHeiner Kallweit pnp_disable_dev(pnp_dev);
140e0f03e87SHeiner Kallweit
1411da177e4SLinus Torvalds pnp_device_detach(pnp_dev);
1421da177e4SLinus Torvalds }
1431da177e4SLinus Torvalds
pnp_device_shutdown(struct device * dev)144abd6633cSDavid Härdeman static void pnp_device_shutdown(struct device *dev)
145abd6633cSDavid Härdeman {
146abd6633cSDavid Härdeman struct pnp_dev *pnp_dev = to_pnp_dev(dev);
147abd6633cSDavid Härdeman struct pnp_driver *drv = pnp_dev->driver;
148abd6633cSDavid Härdeman
149abd6633cSDavid Härdeman if (drv && drv->shutdown)
150abd6633cSDavid Härdeman drv->shutdown(pnp_dev);
151abd6633cSDavid Härdeman }
152abd6633cSDavid Härdeman
pnp_bus_match(struct device * dev,struct device_driver * drv)1531da177e4SLinus Torvalds static int pnp_bus_match(struct device *dev, struct device_driver *drv)
1541da177e4SLinus Torvalds {
1551da177e4SLinus Torvalds struct pnp_dev *pnp_dev = to_pnp_dev(dev);
1561da177e4SLinus Torvalds struct pnp_driver *pnp_drv = to_pnp_driver(drv);
15707d4e9afSBjorn Helgaas
1581da177e4SLinus Torvalds if (match_device(pnp_drv, pnp_dev) == NULL)
1591da177e4SLinus Torvalds return 0;
1601da177e4SLinus Torvalds return 1;
1611da177e4SLinus Torvalds }
1621da177e4SLinus Torvalds
__pnp_bus_suspend(struct device * dev,pm_message_t state)163eaf140b6SShuah Khan static int __pnp_bus_suspend(struct device *dev, pm_message_t state)
1644c98cfefSTakashi Iwai {
1654c98cfefSTakashi Iwai struct pnp_dev *pnp_dev = to_pnp_dev(dev);
1664c98cfefSTakashi Iwai struct pnp_driver *pnp_drv = pnp_dev->driver;
16768094e32SPierre Ossman int error;
1684c98cfefSTakashi Iwai
16968094e32SPierre Ossman if (!pnp_drv)
17068094e32SPierre Ossman return 0;
17168094e32SPierre Ossman
172729377d5SShuah Khan if (pnp_drv->driver.pm && pnp_drv->driver.pm->suspend) {
173729377d5SShuah Khan error = pnp_drv->driver.pm->suspend(dev);
174a759de69SYoungjin Jang suspend_report_result(dev, pnp_drv->driver.pm->suspend, error);
175729377d5SShuah Khan if (error)
176729377d5SShuah Khan return error;
177729377d5SShuah Khan }
178729377d5SShuah Khan
17968094e32SPierre Ossman if (pnp_drv->suspend) {
18068094e32SPierre Ossman error = pnp_drv->suspend(pnp_dev, state);
18168094e32SPierre Ossman if (error)
18268094e32SPierre Ossman return error;
18368094e32SPierre Ossman }
18468094e32SPierre Ossman
185*62a0ec9dSHans de Goede /* can_write is necessary to be able to re-start the device on resume */
186*62a0ec9dSHans de Goede if (pnp_can_disable(pnp_dev) && pnp_can_write(pnp_dev)) {
18768094e32SPierre Ossman error = pnp_stop_dev(pnp_dev);
18868094e32SPierre Ossman if (error)
18968094e32SPierre Ossman return error;
19068094e32SPierre Ossman }
19168094e32SPierre Ossman
19201395d79SPeter Hurley if (pnp_can_suspend(pnp_dev))
193fc30e68eSShaohua Li pnp_dev->protocol->suspend(pnp_dev, state);
1944c98cfefSTakashi Iwai return 0;
1954c98cfefSTakashi Iwai }
1964c98cfefSTakashi Iwai
pnp_bus_suspend(struct device * dev)197eaf140b6SShuah Khan static int pnp_bus_suspend(struct device *dev)
198eaf140b6SShuah Khan {
199eaf140b6SShuah Khan return __pnp_bus_suspend(dev, PMSG_SUSPEND);
200eaf140b6SShuah Khan }
201eaf140b6SShuah Khan
pnp_bus_freeze(struct device * dev)202eaf140b6SShuah Khan static int pnp_bus_freeze(struct device *dev)
203eaf140b6SShuah Khan {
204eaf140b6SShuah Khan return __pnp_bus_suspend(dev, PMSG_FREEZE);
205eaf140b6SShuah Khan }
206eaf140b6SShuah Khan
pnp_bus_poweroff(struct device * dev)2078a37ea50SDmitry Torokhov static int pnp_bus_poweroff(struct device *dev)
2088a37ea50SDmitry Torokhov {
2098a37ea50SDmitry Torokhov return __pnp_bus_suspend(dev, PMSG_HIBERNATE);
2108a37ea50SDmitry Torokhov }
2118a37ea50SDmitry Torokhov
pnp_bus_resume(struct device * dev)21268094e32SPierre Ossman static int pnp_bus_resume(struct device *dev)
2134c98cfefSTakashi Iwai {
2144c98cfefSTakashi Iwai struct pnp_dev *pnp_dev = to_pnp_dev(dev);
2154c98cfefSTakashi Iwai struct pnp_driver *pnp_drv = pnp_dev->driver;
21668094e32SPierre Ossman int error;
2174c98cfefSTakashi Iwai
21868094e32SPierre Ossman if (!pnp_drv)
21968094e32SPierre Ossman return 0;
22068094e32SPierre Ossman
221cc8e7a35SRafael J. Wysocki if (pnp_dev->protocol->resume) {
222cc8e7a35SRafael J. Wysocki error = pnp_dev->protocol->resume(pnp_dev);
223cc8e7a35SRafael J. Wysocki if (error)
224cc8e7a35SRafael J. Wysocki return error;
225cc8e7a35SRafael J. Wysocki }
226fc30e68eSShaohua Li
2275d38998eSRene Herman if (pnp_can_write(pnp_dev)) {
22868094e32SPierre Ossman error = pnp_start_dev(pnp_dev);
22968094e32SPierre Ossman if (error)
23068094e32SPierre Ossman return error;
23168094e32SPierre Ossman }
23268094e32SPierre Ossman
233729377d5SShuah Khan if (pnp_drv->driver.pm && pnp_drv->driver.pm->resume) {
234729377d5SShuah Khan error = pnp_drv->driver.pm->resume(dev);
235729377d5SShuah Khan if (error)
236729377d5SShuah Khan return error;
237729377d5SShuah Khan }
238729377d5SShuah Khan
2395d38998eSRene Herman if (pnp_drv->resume) {
2405d38998eSRene Herman error = pnp_drv->resume(pnp_dev);
2415d38998eSRene Herman if (error)
2425d38998eSRene Herman return error;
2435d38998eSRene Herman }
24468094e32SPierre Ossman
24568094e32SPierre Ossman return 0;
2464c98cfefSTakashi Iwai }
2471da177e4SLinus Torvalds
248eaf140b6SShuah Khan static const struct dev_pm_ops pnp_bus_dev_pm_ops = {
2498a37ea50SDmitry Torokhov /* Suspend callbacks */
250eaf140b6SShuah Khan .suspend = pnp_bus_suspend,
251eaf140b6SShuah Khan .resume = pnp_bus_resume,
2528a37ea50SDmitry Torokhov /* Hibernate callbacks */
2538a37ea50SDmitry Torokhov .freeze = pnp_bus_freeze,
2548a37ea50SDmitry Torokhov .thaw = pnp_bus_resume,
2558a37ea50SDmitry Torokhov .poweroff = pnp_bus_poweroff,
2568a37ea50SDmitry Torokhov .restore = pnp_bus_resume,
257eaf140b6SShuah Khan };
258eaf140b6SShuah Khan
2591da177e4SLinus Torvalds struct bus_type pnp_bus_type = {
2601da177e4SLinus Torvalds .name = "pnp",
2611da177e4SLinus Torvalds .match = pnp_bus_match,
2624681fc32SRussell King .probe = pnp_device_probe,
2634681fc32SRussell King .remove = pnp_device_remove,
264abd6633cSDavid Härdeman .shutdown = pnp_device_shutdown,
265eaf140b6SShuah Khan .pm = &pnp_bus_dev_pm_ops,
2662df43901SGreg Kroah-Hartman .dev_groups = pnp_dev_groups,
2671da177e4SLinus Torvalds };
2681da177e4SLinus Torvalds
pnp_register_driver(struct pnp_driver * drv)2691da177e4SLinus Torvalds int pnp_register_driver(struct pnp_driver *drv)
2701da177e4SLinus Torvalds {
2711da177e4SLinus Torvalds drv->driver.name = drv->name;
2721da177e4SLinus Torvalds drv->driver.bus = &pnp_bus_type;
2731da177e4SLinus Torvalds
274982c6094SBjorn Helgaas return driver_register(&drv->driver);
2751da177e4SLinus Torvalds }
276d02908adSJinchao Wang EXPORT_SYMBOL(pnp_register_driver);
2771da177e4SLinus Torvalds
pnp_unregister_driver(struct pnp_driver * drv)2781da177e4SLinus Torvalds void pnp_unregister_driver(struct pnp_driver *drv)
2791da177e4SLinus Torvalds {
2801da177e4SLinus Torvalds driver_unregister(&drv->driver);
2811da177e4SLinus Torvalds }
282d02908adSJinchao Wang EXPORT_SYMBOL(pnp_unregister_driver);
2831da177e4SLinus Torvalds
2841da177e4SLinus Torvalds /**
2851da177e4SLinus Torvalds * pnp_add_id - adds an EISA id to the specified device
2861da177e4SLinus Torvalds * @dev: pointer to the desired device
287772defc6SBjorn Helgaas * @id: pointer to an EISA id string
2881da177e4SLinus Torvalds */
pnp_add_id(struct pnp_dev * dev,const char * id)289620e112cSThomas Renninger struct pnp_id *pnp_add_id(struct pnp_dev *dev, const char *id)
2901da177e4SLinus Torvalds {
291772defc6SBjorn Helgaas struct pnp_id *dev_id, *ptr;
29207d4e9afSBjorn Helgaas
293772defc6SBjorn Helgaas dev_id = kzalloc(sizeof(struct pnp_id), GFP_KERNEL);
294772defc6SBjorn Helgaas if (!dev_id)
295772defc6SBjorn Helgaas return NULL;
296772defc6SBjorn Helgaas
297772defc6SBjorn Helgaas dev_id->id[0] = id[0];
298772defc6SBjorn Helgaas dev_id->id[1] = id[1];
299772defc6SBjorn Helgaas dev_id->id[2] = id[2];
300772defc6SBjorn Helgaas dev_id->id[3] = tolower(id[3]);
301772defc6SBjorn Helgaas dev_id->id[4] = tolower(id[4]);
302772defc6SBjorn Helgaas dev_id->id[5] = tolower(id[5]);
303772defc6SBjorn Helgaas dev_id->id[6] = tolower(id[6]);
304772defc6SBjorn Helgaas dev_id->id[7] = '\0';
305772defc6SBjorn Helgaas
306772defc6SBjorn Helgaas dev_id->next = NULL;
3071da177e4SLinus Torvalds ptr = dev->id;
3081da177e4SLinus Torvalds while (ptr && ptr->next)
3091da177e4SLinus Torvalds ptr = ptr->next;
3101da177e4SLinus Torvalds if (ptr)
311772defc6SBjorn Helgaas ptr->next = dev_id;
3121da177e4SLinus Torvalds else
313772defc6SBjorn Helgaas dev->id = dev_id;
314772defc6SBjorn Helgaas
315772defc6SBjorn Helgaas return dev_id;
3161da177e4SLinus Torvalds }
317