/* * TODO merge into main bbswitch module. * TODO on ON call device_release_driver * TODO how to bind to a specific device from kernel space? Can't use * driver_probe_device (https://lkml.org/lkml/2014/2/14/628). Maybe use * driver_override or new_id/bind/remove_id from userspace? */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Toggle the discrete graphics card (PCI driver)"); MODULE_AUTHOR("Peter Wu "); static int bbswitch_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { /* TODO how to discover devices? */ /* Prevent kernel from detaching the PCI device for some devices that * generate hotplug events. The graphics card is typically not physically * removable. */ pci_ignore_hotplug(dev); pm_runtime_set_active(&dev->dev); /* clear any errors */ /* Use autosuspend to avoid lspci waking up the device multiple times. */ pm_runtime_set_autosuspend_delay(&dev->dev, 2000); pm_runtime_use_autosuspend(&dev->dev); pm_runtime_allow(&dev->dev); pm_runtime_put_autosuspend(&dev->dev); return 0; } static void bbswitch_pci_remove(struct pci_dev *dev) { pm_runtime_get_noresume(&dev->dev); pm_runtime_dont_use_autosuspend(&dev->dev); pm_runtime_forbid(&dev->dev); } static int bbswitch_runtime_suspend(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); pr_info("disabling discrete graphics\n"); /* TODO if _PR3 is not supported, call Optimus DSM here. */ /* TODO for v1 Optimus, call DSM here. */ /* Save state now that the device is still awake, makes PCI layer happy */ pci_save_state(pdev); /* TODO if _PR3 is supported, should this be PCI_D3hot? */ pci_set_power_state(pdev, PCI_D3cold); return 0; } static int bbswitch_runtime_resume(struct device *dev) { pr_info("enabling discrete graphics\n"); /* TODO for v1 Optimus, call DSM here. */ /* Nothing to do for Optimus, the PCI layer already moved into D0 state. */ return 0; } static struct dev_pm_ops bbswitch_pm_ops = { .runtime_suspend = bbswitch_runtime_suspend, .runtime_resume = bbswitch_runtime_resume, /* No runtime_idle callback, the default zero delay is sufficient. */ }; static struct pci_driver bbswitch_pci_driver = { .name = KBUILD_MODNAME, .id_table = NULL, /* will be added dynamically */ .probe = bbswitch_pci_probe, .remove = bbswitch_pci_remove, .driver.pm = &bbswitch_pm_ops, }; static int __init bbswitch_dev_init(void) { int ret; ret = pci_register_driver(&bbswitch_pci_driver); #if 0 ret = pci_add_dynid(&bbswitch_pci_driver, PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, PCI_BASE_CLASS_DISPLAY << 16, 0xff0000); #endif return ret; } static void __exit bbswitch_dev_exit(void) { pci_unregister_driver(&bbswitch_pci_driver); } module_init(bbswitch_dev_init); module_exit(bbswitch_dev_exit); /* vim: set sw=4 ts=4 et: */