diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst index e519d374c3789..27f95abdbe997 100644 --- a/Documentation/driver-api/surface_aggregator/client.rst +++ b/Documentation/driver-api/surface_aggregator/client.rst @@ -17,6 +17,8 @@ .. |SSAM_DEVICE| replace:: :c:func:`SSAM_DEVICE` .. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` .. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` +.. |ssam_device_notifier_register| replace:: :c:func:`ssam_device_notifier_register` +.. |ssam_device_notifier_unregister| replace:: :c:func:`ssam_device_notifier_unregister` .. |ssam_request_sync| replace:: :c:func:`ssam_request_sync` .. |ssam_event_mask| replace:: :c:type:`enum ssam_event_mask ` @@ -312,7 +314,9 @@ Handling Events To receive events from the SAM EC, an event notifier must be registered for the desired event via |ssam_notifier_register|. The notifier must be unregistered via |ssam_notifier_unregister| once it is not required any -more. +more. For |ssam_device| type clients, the |ssam_device_notifier_register| and +|ssam_device_notifier_unregister| wrappers should be preferred as they properly +handle hot-removal of client devices. Event notifiers are registered by providing (at minimum) a callback to call in case an event has been received, the registry specifying how the event diff --git a/Documentation/userspace-api/media/mediactl/media-controller-model.rst b/Documentation/userspace-api/media/mediactl/media-controller-model.rst index 222cb99debb52..78bfdfb2a322f 100644 --- a/Documentation/userspace-api/media/mediactl/media-controller-model.rst +++ b/Documentation/userspace-api/media/mediactl/media-controller-model.rst @@ -33,3 +33,9 @@ are: - An **interface link** is a point-to-point bidirectional control connection between a Linux Kernel interface and an entity. + +- An **ancillary link** is a point-to-point connection denoting that two + entities form a single logical unit. For example this could represent the + fact that a particular camera sensor and lens controller form a single + physical module, meaning this lens controller drives the lens for this + camera sensor. \ No newline at end of file diff --git a/Documentation/userspace-api/media/mediactl/media-types.rst b/Documentation/userspace-api/media/mediactl/media-types.rst index 0a26397bd01d6..60747251d4090 100644 --- a/Documentation/userspace-api/media/mediactl/media-types.rst +++ b/Documentation/userspace-api/media/mediactl/media-types.rst @@ -412,14 +412,21 @@ must be set for every pad. is set by drivers and is read-only for applications. * - ``MEDIA_LNK_FL_LINK_TYPE`` - - This is a bitmask that defines the type of the link. Currently, - two types of links are supported: + - This is a bitmask that defines the type of the link. The following + link types are currently supported: .. _MEDIA-LNK-FL-DATA-LINK: - ``MEDIA_LNK_FL_DATA_LINK`` if the link is between two pads + ``MEDIA_LNK_FL_DATA_LINK`` for links that represent a data connection + between two pads. .. _MEDIA-LNK-FL-INTERFACE-LINK: - ``MEDIA_LNK_FL_INTERFACE_LINK`` if the link is between an - interface and an entity + ``MEDIA_LNK_FL_INTERFACE_LINK`` for links that associate an entity to its + interface. + + .. _MEDIA-LNK-FL-ANCILLARY-LINK: + + ``MEDIA_LNK_FL_ANCILLARY_LINK`` for links that represent a physical + relationship between two entities. The link may or may not be ummutable, so + applications must not assume either case. \ No newline at end of file diff --git a/MAINTAINERS b/MAINTAINERS index c7c7a96b62a87..03452d847a528 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6002,6 +6002,13 @@ T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/dongwoon,dw9714.txt F: drivers/media/i2c/dw9714.c +DONGWOON DW9719 LENS VOICE COIL DRIVER +M: Daniel Scally +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: drivers/media/i2c/dw9719.c + DONGWOON DW9768 LENS VOICE COIL DRIVER M: Dongchun Zhu L: linux-media@vger.kernel.org @@ -13030,6 +13037,12 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch] F: include/linux/cciss*.h F: include/uapi/linux/cciss*.h +MICROSOFT SURFACE AGGREGATOR TABLET-MODE SWITCH +M: Maximilian Luz +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/surface/surface_aggregator_tablet_switch.c + MICROSOFT SURFACE BATTERY AND AC DRIVERS M: Maximilian Luz L: linux-pm@vger.kernel.org @@ -13101,6 +13114,12 @@ F: include/linux/surface_acpi_notify.h F: include/linux/surface_aggregator/ F: include/uapi/linux/surface_aggregator/ +MICROSOFT SURFACE SYSTEM AGGREGATOR HUB DRIVER +M: Maximilian Luz +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/surface/surface_aggregator_hub.c + MICROTEK X6 SCANNER M: Oliver Neukum S: Maintained diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c index 0d01e7f5078c2..caaec200bea27 100644 --- a/arch/x86/kernel/acpi/boot.c +++ b/arch/x86/kernel/acpi/boot.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -1152,6 +1153,24 @@ static void __init mp_config_acpi_legacy_irqs(void) } } +static const struct dmi_system_id surface_quirk[] __initconst = { + { + .ident = "Microsoft Surface Laptop 4 (AMD 15\")", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") + }, + }, + { + .ident = "Microsoft Surface Laptop 4 (AMD 13\")", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") + }, + }, + {} +}; + /* * Parse IOAPIC related entries in MADT * returns 0 on success, < 0 on error @@ -1207,6 +1226,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, acpi_gbl_FADT.sci_interrupt); + if (dmi_check_system(surface_quirk)) { + pr_warn("Surface hack: Override irq 7\n"); + mp_override_legacy_irq(7, 3, 3, 7); + } + /* Fill in identity legacy mappings where no override */ mp_config_acpi_legacy_irqs(); diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index dc208f5f5a1f7..306513fec1e1f 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -52,7 +52,6 @@ static bool battery_driver_registered; static int battery_bix_broken_package; static int battery_notification_delay_ms; static int battery_ac_is_broken; -static int battery_quirk_notcharging; static unsigned int cache_time = 1000; module_param(cache_time, uint, 0644); MODULE_PARM_DESC(cache_time, "cache time in milliseconds"); @@ -216,10 +215,8 @@ static int acpi_battery_get_property(struct power_supply *psy, val->intval = POWER_SUPPLY_STATUS_CHARGING; else if (acpi_battery_is_charged(battery)) val->intval = POWER_SUPPLY_STATUS_FULL; - else if (battery_quirk_notcharging) - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; else - val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; break; case POWER_SUPPLY_PROP_PRESENT: val->intval = acpi_battery_present(battery); @@ -1105,12 +1102,6 @@ battery_ac_is_broken_quirk(const struct dmi_system_id *d) return 0; } -static int __init battery_quirk_not_charging(const struct dmi_system_id *d) -{ - battery_quirk_notcharging = 1; - return 0; -} - static const struct dmi_system_id bat_dmi_table[] __initconst = { { /* NEC LZ750/LS */ @@ -1139,19 +1130,6 @@ static const struct dmi_system_id bat_dmi_table[] __initconst = { DMI_MATCH(DMI_BIOS_DATE, "08/22/2014"), }, }, - { - /* - * On Lenovo ThinkPads the BIOS specification defines - * a state when the bits for charging and discharging - * are both set to 0. That state is "Not Charging". - */ - .callback = battery_quirk_not_charging, - .ident = "Lenovo ThinkPad", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad"), - }, - }, { /* Microsoft Surface Go 3 */ .callback = battery_notification_delay_quirk, diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 762b61f67e6c6..2c0f39a7f2a14 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -2122,6 +2122,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, static void acpi_default_enumeration(struct acpi_device *device) { + if (!acpi_dev_ready_for_enumeration(device)) + return; + /* * Do not enumerate devices with enumeration_by_parent flag set as * they will be enumerated by their respective parents. diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index d789c077d95dc..feda593a8a832 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -63,6 +63,7 @@ static struct usb_driver btusb_driver; #define BTUSB_INTEL_BROKEN_SHUTDOWN_LED BIT(24) #define BTUSB_INTEL_BROKEN_INITIAL_NCMD BIT(25) #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) +#define BTUSB_LOWER_LESCAN_INTERVAL BIT(27) static const struct usb_device_id btusb_table[] = { /* Generic Bluetooth USB device */ @@ -377,6 +378,7 @@ static const struct usb_device_id blacklist_table[] = { { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, /* Intel Bluetooth devices */ { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, @@ -3790,6 +3792,19 @@ static int btusb_probe(struct usb_interface *intf, if (id->driver_info & BTUSB_MARVELL) hdev->set_bdaddr = btusb_set_bdaddr_marvell; + /* The Marvell 88W8897 combined wifi and bluetooth card is known for + * very bad bt+wifi coexisting performance. + * + * Decrease the passive BT Low Energy scan interval a bit + * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter + * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly + * higher wifi throughput while passively scanning for BT LE devices. + */ + if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { + hdev->le_scan_interval = 0x0190; + hdev->le_scan_window = 0x000a; + } + if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && (id->driver_info & BTUSB_MEDIATEK)) { hdev->setup = btusb_mtk_setup; diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 6bb3890b0f2c9..61142639be269 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -34,7 +34,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -47,6 +50,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); MODULE_LICENSE("GPL"); #include "hid-ids.h" +#include "usbhid/usbhid.h" /* quirks to control the device */ #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) @@ -71,12 +75,18 @@ MODULE_LICENSE("GPL"); #define MT_QUIRK_SEPARATE_APP_REPORT BIT(19) #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) #define MT_QUIRK_DISABLE_WAKEUP BIT(21) +#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(22) +#define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(23) #define MT_INPUTMODE_TOUCHSCREEN 0x02 #define MT_INPUTMODE_TOUCHPAD 0x03 #define MT_BUTTONTYPE_CLICKPAD 0 +#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 +#define MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE 0xff050072 +#define MS_TYPE_COVER_APPLICATION 0xff050050 + enum latency_mode { HID_LATENCY_NORMAL = 0, HID_LATENCY_HIGH = 1, @@ -168,6 +178,8 @@ struct mt_device { struct list_head applications; struct list_head reports; + + struct notifier_block pm_notifier; }; static void mt_post_parse_default_settings(struct mt_device *td, @@ -211,6 +223,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); #define MT_CLS_GOOGLE 0x0111 #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 #define MT_CLS_SMART_TECH 0x0113 +#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 #define MT_DEFAULT_MAXCONTACT 10 #define MT_MAX_MAXCONTACT 250 @@ -386,6 +399,17 @@ static const struct mt_class mt_classes[] = { MT_QUIRK_CONTACT_CNT_ACCURATE | MT_QUIRK_SEPARATE_APP_REPORT, }, + { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, + .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | + MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH | + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_STICKY_FINGERS | + MT_QUIRK_WIN8_PTP_BUTTONS, + .export_all_inputs = true + }, { } }; @@ -1337,6 +1361,9 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, field->application != HID_CP_CONSUMER_CONTROL && field->application != HID_GD_WIRELESS_RADIO_CTLS && field->application != HID_GD_SYSTEM_MULTIAXIS && + !(field->application == MS_TYPE_COVER_APPLICATION && + application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && + usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) && !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && application->quirks & MT_QUIRK_ASUS_CUSTOM_UP)) return -1; @@ -1364,6 +1391,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, return 1; } + /* + * The Microsoft Surface Pro Typecover has a non-standard HID + * tablet mode switch on a vendor specific usage page with vendor + * specific usage. + */ + if (field->application == MS_TYPE_COVER_APPLICATION && + application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && + usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { + usage->type = EV_SW; + usage->code = SW_TABLET_MODE; + *max = SW_MAX; + *bit = hi->input->swbit; + return 1; + } + if (rdata->is_mt_collection) return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, application); @@ -1385,6 +1427,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, { struct mt_device *td = hid_get_drvdata(hdev); struct mt_report_data *rdata; + struct input_dev *input; rdata = mt_find_report_data(td, field->report); if (rdata && rdata->is_mt_collection) { @@ -1392,6 +1435,19 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, return -1; } + /* + * We own an input device which acts as a tablet mode switch for + * the Surface Pro Typecover. + */ + if (field->application == MS_TYPE_COVER_APPLICATION && + rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && + usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { + input = hi->input; + input_set_capability(input, EV_SW, SW_TABLET_MODE); + input_report_switch(input, SW_TABLET_MODE, 0); + return -1; + } + /* let hid-core decide for the others */ return 0; } @@ -1401,11 +1457,21 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, { struct mt_device *td = hid_get_drvdata(hid); struct mt_report_data *rdata; + struct input_dev *input; rdata = mt_find_report_data(td, field->report); if (rdata && rdata->is_mt_collection) return mt_touch_event(hid, field, usage, value); + if (field->application == MS_TYPE_COVER_APPLICATION && + rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && + usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { + input = field->hidinput->input; + input_report_switch(input, SW_TABLET_MODE, (value & 0xFF) != 0x22); + input_sync(input); + return 1; + } + return 0; } @@ -1558,6 +1624,42 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; } +static int get_type_cover_field(struct hid_report_enum *rep_enum, + struct hid_field **field, int usage) +{ + struct hid_report *rep; + struct hid_field *cur_field; + int i, j; + + list_for_each_entry(rep, &rep_enum->report_list, list) { + for (i = 0; i < rep->maxfield; i++) { + cur_field = rep->field[i]; + if (cur_field->application != MS_TYPE_COVER_APPLICATION) + continue; + for (j = 0; j < cur_field->maxusage; j++) { + if (cur_field->usage[j].hid == usage) { + *field = cur_field; + return true; + } + } + } + } + return false; +} + +static void request_type_cover_tablet_mode_switch(struct hid_device *hdev) +{ + struct hid_field *field; + + if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], + &field, + MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { + hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); + } else { + hid_err(hdev, "couldn't find tablet mode field\n"); + } +} + static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) { struct mt_device *td = hid_get_drvdata(hdev); @@ -1607,6 +1709,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) /* force BTN_STYLUS to allow tablet matching in udev */ __set_bit(BTN_STYLUS, hi->input->keybit); break; + case MS_TYPE_COVER_APPLICATION: + if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { + suffix = "Tablet Mode Switch"; + request_type_cover_tablet_mode_switch(hdev); + break; + } + fallthrough; default: suffix = "UNKNOWN"; break; @@ -1695,6 +1804,46 @@ static void mt_expired_timeout(struct timer_list *t) clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); } +static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) +{ + struct usb_device *udev = hid_to_usb_dev(hdev); + struct hid_field *field = NULL; + + /* Wake up the device in case it's already suspended */ + pm_runtime_get_sync(&udev->dev); + + if (!get_type_cover_field(&hdev->report_enum[HID_FEATURE_REPORT], + &field, + MS_TYPE_COVER_FEATURE_REPORT_USAGE)) { + hid_err(hdev, "couldn't find backlight field\n"); + goto out; + } + + field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; + hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); + +out: + pm_runtime_put_sync(&udev->dev); +} + +static int mt_pm_notifier(struct notifier_block *notifier, + unsigned long pm_event, + void *unused) +{ + struct mt_device *td = + container_of(notifier, struct mt_device, pm_notifier); + struct hid_device *hdev = td->hdev; + + if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { + if (pm_event == PM_SUSPEND_PREPARE) + update_keyboard_backlight(hdev, 0); + else if (pm_event == PM_POST_SUSPEND) + update_keyboard_backlight(hdev, 1); + } + + return NOTIFY_DONE; +} + static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) { int ret, i; @@ -1718,6 +1867,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; hid_set_drvdata(hdev, td); + td->pm_notifier.notifier_call = mt_pm_notifier; + register_pm_notifier(&td->pm_notifier); + INIT_LIST_HEAD(&td->applications); INIT_LIST_HEAD(&td->reports); @@ -1747,15 +1899,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) timer_setup(&td->release_timer, mt_expired_timeout, 0); ret = hid_parse(hdev); - if (ret != 0) + if (ret != 0) { + unregister_pm_notifier(&td->pm_notifier); return ret; + } if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) mt_fix_const_fields(hdev, HID_DG_CONTACTID); ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); - if (ret) + if (ret) { + unregister_pm_notifier(&td->pm_notifier); return ret; + } ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); if (ret) @@ -1784,13 +1940,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) static int mt_reset_resume(struct hid_device *hdev) { + struct mt_device *td = hid_get_drvdata(hdev); + mt_release_contacts(hdev); mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); + + /* Request an update on the typecover folding state on resume + * after reset. + */ + if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) + request_type_cover_tablet_mode_switch(hdev); + return 0; } static int mt_resume(struct hid_device *hdev) { + struct mt_device *td = hid_get_drvdata(hdev); + /* Some Elan legacy devices require SET_IDLE to be set on resume. * It should be safe to send it to other devices too. * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ @@ -1799,6 +1966,10 @@ static int mt_resume(struct hid_device *hdev) mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); + /* Request an update on the typecover folding state on resume. */ + if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) + request_type_cover_tablet_mode_switch(hdev); + return 0; } #endif @@ -1806,7 +1977,23 @@ static int mt_resume(struct hid_device *hdev) static void mt_remove(struct hid_device *hdev) { struct mt_device *td = hid_get_drvdata(hdev); + struct hid_field *field; + struct input_dev *input; + /* Reset tablet mode switch on disconnect. */ + if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { + if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], + &field, + MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { + input = field->hidinput->input; + input_report_switch(input, SW_TABLET_MODE, 0); + input_sync(input); + } else { + hid_err(hdev, "couldn't find tablet mode field\n"); + } + } + + unregister_pm_notifier(&td->pm_notifier); del_timer_sync(&td->release_timer); sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); @@ -2180,6 +2367,11 @@ static const struct hid_device_id mt_devices[] = { MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, USB_DEVICE_ID_XIROKU_CSR2) }, + /* Microsoft Surface type cover */ + { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, + USB_VENDOR_ID_MICROSOFT, 0x09c0) }, + /* Google MT devices */ { .driver_data = MT_CLS_GOOGLE, HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c index e46330b2e561a..87637f813de20 100644 --- a/drivers/hid/surface-hid/surface_hid_core.c +++ b/drivers/hid/surface-hid/surface_hid_core.c @@ -19,12 +19,30 @@ #include "surface_hid_core.h" +/* -- Utility functions. ---------------------------------------------------- */ + +static bool surface_hid_is_hot_removed(struct surface_hid_device *shid) +{ + /* + * Non-ssam client devices, i.e. platform client devices, cannot be + * hot-removed. + */ + if (!is_ssam_device(shid->dev)) + return false; + + return ssam_device_is_hot_removed(to_ssam_device(shid->dev)); +} + + /* -- Device descriptor access. --------------------------------------------- */ static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid) { int status; + if (surface_hid_is_hot_removed(shid)) + return -ENODEV; + status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID, (u8 *)&shid->hid_desc, sizeof(shid->hid_desc)); if (status) @@ -61,6 +79,9 @@ static int surface_hid_load_device_attributes(struct surface_hid_device *shid) { int status; + if (surface_hid_is_hot_removed(shid)) + return -ENODEV; + status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS, (u8 *)&shid->attrs, sizeof(shid->attrs)); if (status) @@ -88,9 +109,18 @@ static int surface_hid_start(struct hid_device *hid) static void surface_hid_stop(struct hid_device *hid) { struct surface_hid_device *shid = hid->driver_data; + bool hot_removed; + + /* + * Communication may fail for devices that have been hot-removed. This + * also includes unregistration of HID events, so we need to check this + * here. Only if the device has not been marked as hot-removed, we can + * safely disable events. + */ + hot_removed = surface_hid_is_hot_removed(shid); /* Note: This call will log errors for us, so ignore them here. */ - ssam_notifier_unregister(shid->ctrl, &shid->notif); + __ssam_notifier_unregister(shid->ctrl, &shid->notif, !hot_removed); } static int surface_hid_open(struct hid_device *hid) @@ -109,6 +139,9 @@ static int surface_hid_parse(struct hid_device *hid) u8 *buf; int status; + if (surface_hid_is_hot_removed(shid)) + return -ENODEV; + buf = kzalloc(len, GFP_KERNEL); if (!buf) return -ENOMEM; @@ -126,6 +159,9 @@ static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportn { struct surface_hid_device *shid = hid->driver_data; + if (surface_hid_is_hot_removed(shid)) + return -ENODEV; + if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) return shid->ops.output_report(shid, reportnum, buf, len); diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c index 08b561f0709d5..d7c397bce0f08 100644 --- a/drivers/i2c/i2c-core-acpi.c +++ b/drivers/i2c/i2c-core-acpi.c @@ -619,6 +619,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, return (ret == 1) ? 0 : -EIO; } +static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, + u8 *data, u8 data_len) +{ + struct i2c_msg msgs[1]; + int ret = AE_OK; + + msgs[0].addr = client->addr; + msgs[0].flags = client->flags; + msgs[0].len = data_len + 1; + msgs[0].buf = data; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + + if (ret < 0) { + dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); + return ret; + } + + /* 1 transfer must have completed successfully */ + return (ret == 1) ? 0 : -EIO; +} + static acpi_status i2c_acpi_space_handler(u32 function, acpi_physical_address command, u32 bits, u64 *value64, @@ -720,6 +742,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, } break; + case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: + if (action == ACPI_READ) { + dev_warn(&adapter->dev, + "protocol 0x%02x not supported for client 0x%02x\n", + accessor_type, client->addr); + ret = AE_BAD_PARAMETER; + goto err; + } else { + status = acpi_gsb_i2c_write_raw_bytes(client, + gsb->data, info->access_length); + } + break; + default: dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", accessor_type, client->addr); diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c index 480476121c010..36e1bf7b7a017 100644 --- a/drivers/input/misc/soc_button_array.c +++ b/drivers/input/misc/soc_button_array.c @@ -495,8 +495,8 @@ static const struct soc_device_data soc_device_MSHW0028 = { * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned * devices use MSHW0040 for power and volume buttons, however the way they * have to be addressed differs. Make sure that we only load this drivers - * for the correct devices by checking the OEM Platform Revision provided by - * the _DSM method. + * for the correct devices by checking if the OEM Platform Revision DSM call + * exists. */ #define MSHW0040_DSM_REVISION 0x01 #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision @@ -507,31 +507,14 @@ static const guid_t MSHW0040_DSM_UUID = static int soc_device_check_MSHW0040(struct device *dev) { acpi_handle handle = ACPI_HANDLE(dev); - union acpi_object *result; - u64 oem_platform_rev = 0; // valid revisions are nonzero - - // get OEM platform revision - result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, - MSHW0040_DSM_REVISION, - MSHW0040_DSM_GET_OMPR, NULL, - ACPI_TYPE_INTEGER); - - if (result) { - oem_platform_rev = result->integer.value; - ACPI_FREE(result); - } - - /* - * If the revision is zero here, the _DSM evaluation has failed. This - * indicates that we have a Pro 4 or Book 1 and this driver should not - * be used. - */ - if (oem_platform_rev == 0) - return -ENODEV; + bool exists; - dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); + // check if OEM platform revision DSM call exists + exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, + MSHW0040_DSM_REVISION, + BIT(MSHW0040_DSM_GET_OMPR)); - return 0; + return exists ? 0 : -ENODEV; } /* diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c index c7ec5177cf78a..0ee615daadeb3 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -37,6 +37,14 @@ #define IS_GFX_DEVICE(pdev) ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY) #define IS_USB_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_SERIAL_USB) #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) +#define IS_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ + ((pdev)->device == 0x9a19 || \ + (pdev)->device == 0x9a39 || \ + (pdev)->device == 0x4e19 || \ + (pdev)->device == 0x465d || \ + (pdev)->device == 0x1919)) +#define IS_IPTS(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ + ((pdev)->device == 0x9d3e)) #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) #define IOAPIC_RANGE_START (0xfee00000) @@ -307,12 +315,16 @@ int intel_iommu_enabled = 0; EXPORT_SYMBOL_GPL(intel_iommu_enabled); static int dmar_map_gfx = 1; +static int dmar_map_ipts = 1; +static int dmar_map_ipu = 1; static int intel_iommu_superpage = 1; static int iommu_identity_mapping; static int iommu_skip_te_disable; #define IDENTMAP_GFX 2 #define IDENTMAP_AZALIA 4 +#define IDENTMAP_IPU 8 +#define IDENTMAP_IPTS 16 int intel_iommu_gfx_mapped; EXPORT_SYMBOL_GPL(intel_iommu_gfx_mapped); @@ -2700,6 +2712,12 @@ static int device_def_domain_type(struct device *dev) if ((iommu_identity_mapping & IDENTMAP_GFX) && IS_GFX_DEVICE(pdev)) return IOMMU_DOMAIN_IDENTITY; + + if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) + return IOMMU_DOMAIN_IDENTITY; + + if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) + return IOMMU_DOMAIN_IDENTITY; } return 0; @@ -3136,6 +3154,12 @@ static int __init init_dmars(void) if (!dmar_map_gfx) iommu_identity_mapping |= IDENTMAP_GFX; + if (!dmar_map_ipu) + iommu_identity_mapping |= IDENTMAP_IPU; + + if (!dmar_map_ipts) + iommu_identity_mapping |= IDENTMAP_IPTS; + check_tylersburg_isoch(); ret = si_domain_init(hw_pass_through); @@ -4907,6 +4931,30 @@ static void quirk_iommu_igfx(struct pci_dev *dev) dmar_map_gfx = 0; } +static void quirk_iommu_ipu(struct pci_dev *dev) +{ + if (!IS_INTEL_IPU(dev)) + return; + + if (risky_device(dev)) + return; + + pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); + dmar_map_ipu = 0; +} + +static void quirk_iommu_ipts(struct pci_dev *dev) +{ + if (!IS_IPTS(dev)) + return; + + if (risky_device(dev)) + return; + + pci_info(dev, "Passthrough IOMMU for IPTS\n"); + dmar_map_ipts = 0; +} + /* G4x/GM45 integrated gfx dmar support is totally busted. */ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); @@ -4942,6 +4990,12 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); +/* disable IPU dmar support */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_iommu_ipu); + +/* disable IPTS dmar support */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); + static void quirk_iommu_rwbf(struct pci_dev *dev) { if (risky_device(dev)) diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index c926e5d43820c..50ea62e637843 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -806,6 +806,17 @@ config VIDEO_DW9714 capability. This is designed for linear control of voice coil motors, controlled via I2C serial interface. +config VIDEO_DW9719 + tristate "DW9719 lens voice coil support" + depends on I2C && VIDEO_DEV + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_ASYNC + help + This is a driver for the DW9719 camera lens voice coil. + This is designed for linear control of voice coil motors, + controlled via I2C serial interface. + config VIDEO_DW9768 tristate "DW9768 lens voice coil support" depends on I2C && VIDEO_DEV diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 3e1696963e7f8..9dfda069e0063 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_VIDEO_CS5345) += cs5345.o obj-$(CONFIG_VIDEO_CS53L32A) += cs53l32a.o obj-$(CONFIG_VIDEO_CX25840) += cx25840/ obj-$(CONFIG_VIDEO_DW9714) += dw9714.o +obj-$(CONFIG_VIDEO_DW9719) += dw9719.o obj-$(CONFIG_VIDEO_DW9768) += dw9768.o obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o obj-$(CONFIG_VIDEO_ET8EK8) += et8ek8/ diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c new file mode 100644 index 0000000000000..8451c75b696b6 --- /dev/null +++ b/drivers/media/i2c/dw9719.c @@ -0,0 +1,427 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2012 Intel Corporation + +/* + * Based on linux/modules/camera/drivers/media/i2c/imx/dw9719.c in this repo: + * https://github.com/ZenfoneArea/android_kernel_asus_zenfone5 + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DW9719_MAX_FOCUS_POS 1023 +#define DW9719_CTRL_STEPS 16 +#define DW9719_CTRL_DELAY_US 1000 +#define DELAY_MAX_PER_STEP_NS (1000000 * 1023) + +#define DW9719_INFO 0 +#define DW9719_ID 0xF1 +#define DW9719_CONTROL 2 +#define DW9719_VCM_CURRENT 3 + +#define DW9719_MODE 6 +#define DW9719_VCM_FREQ 7 + +#define DW9719_MODE_SAC3 0x40 +#define DW9719_DEFAULT_VCM_FREQ 0x60 +#define DW9719_ENABLE_RINGING 0x02 + +#define NUM_REGULATORS 2 + +#define to_dw9719_device(x) container_of(x, struct dw9719_device, sd) + +struct dw9719_device { + struct device *dev; + struct i2c_client *client; + struct regulator_bulk_data regulators[NUM_REGULATORS]; + struct v4l2_subdev sd; + + struct dw9719_v4l2_ctrls { + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl *focus; + } ctrls; +}; + +static int dw9719_i2c_rd8(struct i2c_client *client, u8 reg, u8 *val) +{ + struct i2c_msg msg[2]; + u8 buf[2] = { reg }; + int ret; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = buf; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 1; + msg[1].buf = &buf[1]; + *val = 0; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) + return ret; + + *val = buf[1]; + + return 0; +} + +static int dw9719_i2c_wr8(struct i2c_client *client, u8 reg, u8 val) +{ + struct i2c_msg msg; + int ret; + + u8 buf[2] = { reg, val }; + + msg.addr = client->addr; + msg.flags = 0; + msg.len = sizeof(buf); + msg.buf = buf; + + ret = i2c_transfer(client->adapter, &msg, 1); + + return ret < 0 ? ret : 0; +} + +static int dw9719_i2c_wr16(struct i2c_client *client, u8 reg, u16 val) +{ + struct i2c_msg msg; + u8 buf[3] = { reg }; + int ret; + + put_unaligned_be16(val, buf + 1); + + msg.addr = client->addr; + msg.flags = 0; + msg.len = sizeof(buf); + msg.buf = buf; + + ret = i2c_transfer(client->adapter, &msg, 1); + + return ret < 0 ? ret : 0; +} + +static int dw9719_detect(struct dw9719_device *dw9719) +{ + int ret; + u8 val; + + ret = dw9719_i2c_rd8(dw9719->client, DW9719_INFO, &val); + if (ret < 0) + return ret; + + if (val != DW9719_ID) { + dev_err(dw9719->dev, "Failed to detect correct id\n"); + ret = -ENXIO; + } + + return 0; +} + +static int dw9719_power_down(struct dw9719_device *dw9719) +{ + return regulator_bulk_disable(NUM_REGULATORS, dw9719->regulators); +} + +static int dw9719_power_up(struct dw9719_device *dw9719) +{ + int ret; + + ret = regulator_bulk_enable(NUM_REGULATORS, dw9719->regulators); + if (ret) + return ret; + + /* Jiggle SCL pin to wake up device */ + ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, 1); + + /* Need 100us to transit from SHUTDOWN to STANDBY*/ + usleep_range(100, 1000); + + ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, + DW9719_ENABLE_RINGING); + if (ret < 0) + goto fail_powerdown; + + ret = dw9719_i2c_wr8(dw9719->client, DW9719_MODE, DW9719_MODE_SAC3); + if (ret < 0) + goto fail_powerdown; + + ret = dw9719_i2c_wr8(dw9719->client, DW9719_VCM_FREQ, + DW9719_DEFAULT_VCM_FREQ); + if (ret < 0) + goto fail_powerdown; + + return 0; + +fail_powerdown: + dw9719_power_down(dw9719); + return ret; +} + +static int dw9719_t_focus_abs(struct dw9719_device *dw9719, s32 value) +{ + int ret; + + value = clamp(value, 0, DW9719_MAX_FOCUS_POS); + ret = dw9719_i2c_wr16(dw9719->client, DW9719_VCM_CURRENT, value); + if (ret < 0) + return ret; + + return 0; +} + +static int dw9719_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct dw9719_device *dw9719 = container_of(ctrl->handler, + struct dw9719_device, + ctrls.handler); + int ret; + + /* Only apply changes to the controls if the device is powered up */ + if (!pm_runtime_get_if_in_use(dw9719->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_FOCUS_ABSOLUTE: + ret = dw9719_t_focus_abs(dw9719, ctrl->val); + break; + default: + ret = -EINVAL; + } + + pm_runtime_put(dw9719->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops dw9719_ctrl_ops = { + .s_ctrl = dw9719_set_ctrl, +}; + +static int __maybe_unused dw9719_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct dw9719_device *dw9719 = to_dw9719_device(sd); + int ret; + int val; + + for (val = dw9719->ctrls.focus->val; val >= 0; + val -= DW9719_CTRL_STEPS) { + ret = dw9719_t_focus_abs(dw9719, val); + if (ret) + return ret; + + usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); + } + + return dw9719_power_down(dw9719); +} + +static int __maybe_unused dw9719_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct dw9719_device *dw9719 = to_dw9719_device(sd); + int current_focus = dw9719->ctrls.focus->val; + int ret; + int val; + + ret = dw9719_power_up(dw9719); + if (ret) + return ret; + + for (val = current_focus % DW9719_CTRL_STEPS; val < current_focus; + val += DW9719_CTRL_STEPS) { + ret = dw9719_t_focus_abs(dw9719, val); + if (ret) + goto err_power_down; + + usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); + } + + return 0; + +err_power_down: + dw9719_power_down(dw9719); + return ret; +} + +static int dw9719_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return pm_runtime_resume_and_get(sd->dev); +} + +static int dw9719_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + pm_runtime_put(sd->dev); + + return 0; +} + +static const struct v4l2_subdev_internal_ops dw9719_internal_ops = { + .open = dw9719_open, + .close = dw9719_close, +}; + +static int dw9719_init_controls(struct dw9719_device *dw9719) +{ + const struct v4l2_ctrl_ops *ops = &dw9719_ctrl_ops; + int ret; + + ret = v4l2_ctrl_handler_init(&dw9719->ctrls.handler, 1); + if (ret) + return ret; + + dw9719->ctrls.focus = v4l2_ctrl_new_std(&dw9719->ctrls.handler, ops, + V4L2_CID_FOCUS_ABSOLUTE, 0, + DW9719_MAX_FOCUS_POS, 1, 0); + + if (dw9719->ctrls.handler.error) { + dev_err(dw9719->dev, "Error initialising v4l2 ctrls\n"); + ret = dw9719->ctrls.handler.error; + goto err_free_handler; + } + + dw9719->sd.ctrl_handler = &dw9719->ctrls.handler; + + return ret; + +err_free_handler: + v4l2_ctrl_handler_free(&dw9719->ctrls.handler); + return ret; +} + +static const struct v4l2_subdev_ops dw9719_ops = { }; + +static int dw9719_probe(struct i2c_client *client) +{ + struct dw9719_device *dw9719; + int ret; + + dw9719 = devm_kzalloc(&client->dev, sizeof(*dw9719), GFP_KERNEL); + if (!dw9719) + return -ENOMEM; + + dw9719->client = client; + dw9719->dev = &client->dev; + + dw9719->regulators[0].supply = "vdd"; + /* + * The DW9719 has only the 1 VDD voltage input, but some PMICs such as + * the TPS68470 PMIC have I2C passthrough capability, to disconnect the + * sensor's I2C pins from the I2C bus when the sensors VSIO (Sensor-IO) + * is off, because some sensors then short these pins to ground; + * and the DW9719 might sit behind this passthrough, this it needs to + * enable VSIO as that will also enable the I2C passthrough. + */ + dw9719->regulators[1].supply = "vsio"; + + ret = devm_regulator_bulk_get(&client->dev, NUM_REGULATORS, + dw9719->regulators); + if (ret) + return dev_err_probe(&client->dev, ret, "getting regulators\n"); + + v4l2_i2c_subdev_init(&dw9719->sd, client, &dw9719_ops); + dw9719->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + dw9719->sd.internal_ops = &dw9719_internal_ops; + + ret = dw9719_init_controls(dw9719); + if (ret) + return ret; + + ret = media_entity_pads_init(&dw9719->sd.entity, 0, NULL); + if (ret < 0) + goto err_free_ctrl_handler; + + dw9719->sd.entity.function = MEDIA_ENT_F_LENS; + + /* + * We need the driver to work in the event that pm runtime is disable in + * the kernel, so power up and verify the chip now. In the event that + * runtime pm is disabled this will leave the chip on, so that the lens + * will work. + */ + + ret = dw9719_power_up(dw9719); + if (ret) + goto err_cleanup_media; + + ret = dw9719_detect(dw9719); + if (ret) + goto err_powerdown; + + pm_runtime_set_active(&client->dev); + pm_runtime_get_noresume(&client->dev); + pm_runtime_enable(&client->dev); + + ret = v4l2_async_register_subdev(&dw9719->sd); + if (ret < 0) + goto err_pm_runtime; + + pm_runtime_set_autosuspend_delay(&client->dev, 1000); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + return ret; + +err_pm_runtime: + pm_runtime_disable(&client->dev); + pm_runtime_put_noidle(&client->dev); +err_powerdown: + dw9719_power_down(dw9719); +err_cleanup_media: + media_entity_cleanup(&dw9719->sd.entity); +err_free_ctrl_handler: + v4l2_ctrl_handler_free(&dw9719->ctrls.handler); + + return ret; +} + +static int dw9719_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9719_device *dw9719 = container_of(sd, struct dw9719_device, + sd); + + pm_runtime_disable(&client->dev); + v4l2_async_unregister_subdev(sd); + v4l2_ctrl_handler_free(&dw9719->ctrls.handler); + media_entity_cleanup(&dw9719->sd.entity); + + return 0; +} + +static const struct i2c_device_id dw9719_id_table[] = { + { "dw9719" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, dw9719_id_table); + +static const struct dev_pm_ops dw9719_pm_ops = { + SET_RUNTIME_PM_OPS(dw9719_suspend, dw9719_resume, NULL) +}; + +static struct i2c_driver dw9719_i2c_driver = { + .driver = { + .name = "dw9719", + .pm = &dw9719_pm_ops, + }, + .probe_new = dw9719_probe, + .remove = dw9719_remove, + .id_table = dw9719_id_table, +}; +module_i2c_driver(dw9719_i2c_driver); + +MODULE_AUTHOR("Daniel Scally "); +MODULE_DESCRIPTION("DW9719 VCM Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/mc/mc-entity.c b/drivers/media/mc/mc-entity.c index 8ab0913d8d823..1ff60d411ea9c 100644 --- a/drivers/media/mc/mc-entity.c +++ b/drivers/media/mc/mc-entity.c @@ -44,6 +44,20 @@ static inline const char *intf_type(struct media_interface *intf) } }; +static inline const char *link_type_name(struct media_link *link) +{ + switch (link->flags & MEDIA_LNK_FL_LINK_TYPE) { + case MEDIA_LNK_FL_DATA_LINK: + return "data"; + case MEDIA_LNK_FL_INTERFACE_LINK: + return "interface"; + case MEDIA_LNK_FL_ANCILLARY_LINK: + return "ancillary"; + default: + return "unknown"; + } +} + __must_check int __media_entity_enum_init(struct media_entity_enum *ent_enum, int idx_max) { @@ -89,9 +103,7 @@ static void dev_dbg_obj(const char *event_name, struct media_gobj *gobj) dev_dbg(gobj->mdev->dev, "%s id %u: %s link id %u ==> id %u\n", - event_name, media_id(gobj), - media_type(link->gobj0) == MEDIA_GRAPH_PAD ? - "data" : "interface", + event_name, media_id(gobj), link_type_name(link), media_id(link->gobj0), media_id(link->gobj1)); break; @@ -295,6 +307,12 @@ static void media_graph_walk_iter(struct media_graph *graph) link = list_entry(link_top(graph), typeof(*link), list); + /* If the link is not a data link, don't follow it */ + if ((link->flags & MEDIA_LNK_FL_LINK_TYPE) != MEDIA_LNK_FL_DATA_LINK) { + link_top(graph) = link_top(graph)->next; + return; + } + /* The link is not enabled so we do not follow. */ if (!(link->flags & MEDIA_LNK_FL_ENABLED)) { link_top(graph) = link_top(graph)->next; @@ -1007,3 +1025,25 @@ void media_remove_intf_links(struct media_interface *intf) mutex_unlock(&mdev->graph_mutex); } EXPORT_SYMBOL_GPL(media_remove_intf_links); + +struct media_link *media_create_ancillary_link(struct media_entity *primary, + struct media_entity *ancillary) +{ + struct media_link *link; + + link = media_add_link(&primary->links); + if (!link) + return ERR_PTR(-ENOMEM); + + link->gobj0 = &primary->graph_obj; + link->gobj1 = &ancillary->graph_obj; + link->flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_ANCILLARY_LINK; + + /* Initialize graph object embedded in the new link */ + media_gobj_create(primary->graph_obj.mdev, MEDIA_GRAPH_LINK, + &link->graph_obj); + + return link; +} +EXPORT_SYMBOL_GPL(media_create_ancillary_link); diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c index 0e9b0503b62a0..ff79582a583d5 100644 --- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c @@ -1382,7 +1382,10 @@ static int cio2_notifier_bound(struct v4l2_async_notifier *notifier, { struct cio2_device *cio2 = to_cio2_device(notifier); struct sensor_async_subdev *s_asd = to_sensor_asd(asd); + struct device *dev = &cio2->pci_dev->dev; struct cio2_queue *q; + unsigned int pad; + int ret; if (cio2->queue[s_asd->csi2.port].sensor) return -EBUSY; @@ -1393,7 +1396,26 @@ static int cio2_notifier_bound(struct v4l2_async_notifier *notifier, q->sensor = sd; q->csi_rx_base = cio2->base + CIO2_REG_PIPE_BASE(q->csi2.port); - return 0; + for (pad = 0; pad < q->sensor->entity.num_pads; pad++) + if (q->sensor->entity.pads[pad].flags & + MEDIA_PAD_FL_SOURCE) + break; + + if (pad == q->sensor->entity.num_pads) { + dev_err(dev, "failed to find src pad for %s\n", + q->sensor->name); + return -ENXIO; + } + + ret = media_create_pad_link(&q->sensor->entity, pad, &q->subdev.entity, + CIO2_PAD_SINK, 0); + if (ret) { + dev_err(dev, "failed to create link for %s\n", + q->sensor->name); + return ret; + } + + return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); } /* The .unbind callback */ @@ -1411,38 +1433,6 @@ static void cio2_notifier_unbind(struct v4l2_async_notifier *notifier, static int cio2_notifier_complete(struct v4l2_async_notifier *notifier) { struct cio2_device *cio2 = to_cio2_device(notifier); - struct device *dev = &cio2->pci_dev->dev; - struct sensor_async_subdev *s_asd; - struct v4l2_async_subdev *asd; - struct cio2_queue *q; - unsigned int pad; - int ret; - - list_for_each_entry(asd, &cio2->notifier.asd_list, asd_list) { - s_asd = to_sensor_asd(asd); - q = &cio2->queue[s_asd->csi2.port]; - - for (pad = 0; pad < q->sensor->entity.num_pads; pad++) - if (q->sensor->entity.pads[pad].flags & - MEDIA_PAD_FL_SOURCE) - break; - - if (pad == q->sensor->entity.num_pads) { - dev_err(dev, "failed to find src pad for %s\n", - q->sensor->name); - return -ENXIO; - } - - ret = media_create_pad_link( - &q->sensor->entity, pad, - &q->subdev.entity, CIO2_PAD_SINK, - 0); - if (ret) { - dev_err(dev, "failed to create link for %s\n", - q->sensor->name); - return ret; - } - } return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); } diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c index 0404267f1ae4f..436bd6900fd8e 100644 --- a/drivers/media/v4l2-core/v4l2-async.c +++ b/drivers/media/v4l2-core/v4l2-async.c @@ -275,6 +275,24 @@ v4l2_async_nf_try_complete(struct v4l2_async_notifier *notifier) static int v4l2_async_nf_try_all_subdevs(struct v4l2_async_notifier *notifier); +static int v4l2_async_create_ancillary_links(struct v4l2_async_notifier *n, + struct v4l2_subdev *sd) +{ + struct media_link *link = NULL; + +#if IS_ENABLED(CONFIG_MEDIA_CONTROLLER) + + if (sd->entity.function != MEDIA_ENT_F_LENS && + sd->entity.function != MEDIA_ENT_F_FLASH) + return 0; + + link = media_create_ancillary_link(&n->sd->entity, &sd->entity); + +#endif + + return IS_ERR(link) ? PTR_ERR(link) : 0; +} + static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier, struct v4l2_device *v4l2_dev, struct v4l2_subdev *sd, @@ -293,6 +311,19 @@ static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier, return ret; } + /* + * Depending of the function of the entities involved, we may want to + * create links between them (for example between a sensor and its lens + * or between a sensor's source pad and the connected device's sink + * pad). + */ + ret = v4l2_async_create_ancillary_links(notifier, sd); + if (ret) { + v4l2_async_nf_call_unbind(notifier, sd, asd); + v4l2_device_unregister_subdev(sd); + return ret; + } + /* Remove from the waiting list */ list_del(&asd->list); sd->asd = asd; diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 41d2bb0ae23a3..effb258d4848a 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -500,4 +500,5 @@ source "drivers/misc/cardreader/Kconfig" source "drivers/misc/habanalabs/Kconfig" source "drivers/misc/uacce/Kconfig" source "drivers/misc/pvpanic/Kconfig" +source "drivers/misc/ipts/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 70e800e9127f8..a8d1e94470250 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -60,3 +60,4 @@ obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o obj-$(CONFIG_OPEN_DICE) += open-dice.o +obj-$(CONFIG_MISC_IPTS) += ipts/ diff --git a/drivers/misc/ipts/Kconfig b/drivers/misc/ipts/Kconfig new file mode 100644 index 0000000000000..83e2a930c396c --- /dev/null +++ b/drivers/misc/ipts/Kconfig @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +config MISC_IPTS + tristate "Intel Precise Touch & Stylus" + depends on INTEL_MEI + help + Say Y here if your system has a touchscreen using Intels + Precise Touch & Stylus (IPTS) technology. + + If unsure say N. + + To compile this driver as a module, choose M here: the + module will be called ipts. + + Building this driver alone will not give you a working touchscreen. + It only exposed a userspace API that can be used by a daemon to + receive and process data from the touchscreen hardware. diff --git a/drivers/misc/ipts/Makefile b/drivers/misc/ipts/Makefile new file mode 100644 index 0000000000000..8f58b9adbc94f --- /dev/null +++ b/drivers/misc/ipts/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Makefile for the IPTS touchscreen driver +# + +obj-$(CONFIG_MISC_IPTS) += ipts.o +ipts-objs := control.o +ipts-objs += mei.o +ipts-objs += receiver.o +ipts-objs += resources.o +ipts-objs += uapi.o + diff --git a/drivers/misc/ipts/context.h b/drivers/misc/ipts/context.h new file mode 100644 index 0000000000000..f4b06a2d3f723 --- /dev/null +++ b/drivers/misc/ipts/context.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef _IPTS_CONTEXT_H_ +#define _IPTS_CONTEXT_H_ + +#include +#include +#include +#include + +#include "protocol.h" + +enum ipts_host_status { + IPTS_HOST_STATUS_STARTING, + IPTS_HOST_STATUS_STARTED, + IPTS_HOST_STATUS_STOPPING, + IPTS_HOST_STATUS_STOPPED, +}; + +struct ipts_buffer_info { + u8 *address; + dma_addr_t dma_address; +}; + +struct ipts_context { + struct mei_cl_device *cldev; + struct device *dev; + + bool restart; + enum ipts_host_status status; + struct ipts_get_device_info_rsp device_info; + + struct ipts_buffer_info data[IPTS_BUFFERS]; + struct ipts_buffer_info doorbell; + + struct ipts_buffer_info feedback[IPTS_BUFFERS]; + struct ipts_buffer_info workqueue; + struct ipts_buffer_info host2me; +}; + +#endif /* _IPTS_CONTEXT_H_ */ diff --git a/drivers/misc/ipts/control.c b/drivers/misc/ipts/control.c new file mode 100644 index 0000000000000..a1d1f97a13d7d --- /dev/null +++ b/drivers/misc/ipts/control.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include + +#include "context.h" +#include "protocol.h" +#include "resources.h" +#include "uapi.h" + +int ipts_control_send(struct ipts_context *ipts, u32 code, void *payload, + size_t size) +{ + int ret; + struct ipts_command cmd; + + memset(&cmd, 0, sizeof(struct ipts_command)); + cmd.code = code; + + if (payload && size > 0) + memcpy(&cmd.payload, payload, size); + + ret = mei_cldev_send(ipts->cldev, (u8 *)&cmd, sizeof(cmd.code) + size); + if (ret >= 0) + return 0; + + /* + * During shutdown the device might get pulled away from below our feet. + * Dont log an error in this case, because it will confuse people. + */ + if (ret != -ENODEV || ipts->status != IPTS_HOST_STATUS_STOPPING) + dev_err(ipts->dev, "Error while sending: 0x%X:%d\n", code, ret); + + return ret; +} + +int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) +{ + struct ipts_feedback_cmd cmd; + + memset(&cmd, 0, sizeof(struct ipts_feedback_cmd)); + cmd.buffer = buffer; + + return ipts_control_send(ipts, IPTS_CMD_FEEDBACK, &cmd, + sizeof(struct ipts_feedback_cmd)); +} + +int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value) +{ + struct ipts_feedback_buffer *feedback; + + memset(ipts->host2me.address, 0, ipts->device_info.feedback_size); + feedback = (struct ipts_feedback_buffer *)ipts->host2me.address; + + feedback->cmd_type = IPTS_FEEDBACK_CMD_TYPE_NONE; + feedback->data_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; + feedback->buffer = IPTS_HOST2ME_BUFFER; + feedback->size = 2; + feedback->payload[0] = report; + feedback->payload[1] = value; + + return ipts_control_send_feedback(ipts, IPTS_HOST2ME_BUFFER); +} + +int ipts_control_start(struct ipts_context *ipts) +{ + if (ipts->status != IPTS_HOST_STATUS_STOPPED) + return -EBUSY; + + dev_info(ipts->dev, "Starting IPTS\n"); + ipts->status = IPTS_HOST_STATUS_STARTING; + ipts->restart = false; + + ipts_uapi_link(ipts); + return ipts_control_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); +} + +int ipts_control_stop(struct ipts_context *ipts) +{ + int ret; + + if (ipts->status == IPTS_HOST_STATUS_STOPPING) + return -EBUSY; + + if (ipts->status == IPTS_HOST_STATUS_STOPPED) + return -EBUSY; + + dev_info(ipts->dev, "Stopping IPTS\n"); + ipts->status = IPTS_HOST_STATUS_STOPPING; + + ipts_uapi_unlink(); + ipts_resources_free(ipts); + + ret = ipts_control_send_feedback(ipts, 0); + if (ret == -ENODEV) + ipts->status = IPTS_HOST_STATUS_STOPPED; + + return ret; +} + +int ipts_control_restart(struct ipts_context *ipts) +{ + if (ipts->restart) + return -EBUSY; + + ipts->restart = true; + return ipts_control_stop(ipts); +} diff --git a/drivers/misc/ipts/control.h b/drivers/misc/ipts/control.h new file mode 100644 index 0000000000000..2c44e9e0e99fc --- /dev/null +++ b/drivers/misc/ipts/control.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef _IPTS_CONTROL_H_ +#define _IPTS_CONTROL_H_ + +#include + +#include "context.h" + +int ipts_control_send(struct ipts_context *ipts, u32 cmd, void *payload, + size_t size); +int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); +int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value); +int ipts_control_start(struct ipts_context *ipts); +int ipts_control_restart(struct ipts_context *ipts); +int ipts_control_stop(struct ipts_context *ipts); + +#endif /* _IPTS_CONTROL_H_ */ diff --git a/drivers/misc/ipts/mei.c b/drivers/misc/ipts/mei.c new file mode 100644 index 0000000000000..59ecf13e00d22 --- /dev/null +++ b/drivers/misc/ipts/mei.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include +#include + +#include "context.h" +#include "control.h" +#include "protocol.h" +#include "receiver.h" +#include "uapi.h" + +static int ipts_mei_set_dma_mask(struct mei_cl_device *cldev) +{ + int ret; + + ret = dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64)); + if (!ret) + return 0; + + return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); +} + +static int ipts_mei_probe(struct mei_cl_device *cldev, + const struct mei_cl_device_id *id) +{ + int ret; + struct ipts_context *ipts; + + if (ipts_mei_set_dma_mask(cldev)) { + dev_err(&cldev->dev, "Failed to set DMA mask for IPTS\n"); + return -EFAULT; + } + + ret = mei_cldev_enable(cldev); + if (ret) { + dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); + return ret; + } + + ipts = kzalloc(sizeof(*ipts), GFP_KERNEL); + if (!ipts) { + mei_cldev_disable(cldev); + return -ENOMEM; + } + + ipts->cldev = cldev; + ipts->dev = &cldev->dev; + ipts->status = IPTS_HOST_STATUS_STOPPED; + + mei_cldev_set_drvdata(cldev, ipts); + mei_cldev_register_rx_cb(cldev, ipts_receiver_callback); + + return ipts_control_start(ipts); +} + +static void ipts_mei_remove(struct mei_cl_device *cldev) +{ + int i; + struct ipts_context *ipts = mei_cldev_get_drvdata(cldev); + + ipts_control_stop(ipts); + + for (i = 0; i < 20; i++) { + if (ipts->status == IPTS_HOST_STATUS_STOPPED) + break; + + msleep(25); + } + + mei_cldev_disable(cldev); + kfree(ipts); +} + +static struct mei_cl_device_id ipts_mei_device_id_table[] = { + { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY }, + {}, +}; +MODULE_DEVICE_TABLE(mei, ipts_mei_device_id_table); + +static struct mei_cl_driver ipts_mei_driver = { + .id_table = ipts_mei_device_id_table, + .name = "ipts", + .probe = ipts_mei_probe, + .remove = ipts_mei_remove, +}; + +static int __init ipts_mei_init(void) +{ + int ret; + + ret = ipts_uapi_init(); + if (ret) + return ret; + + ret = mei_cldev_driver_register(&ipts_mei_driver); + if (ret) { + ipts_uapi_free(); + return ret; + } + + return 0; +} + +static void __exit ipts_mei_exit(void) +{ + mei_cldev_driver_unregister(&ipts_mei_driver); + ipts_uapi_free(); +} + +MODULE_DESCRIPTION("IPTS touchscreen driver"); +MODULE_AUTHOR("Dorian Stoll "); +MODULE_LICENSE("GPL"); + +module_init(ipts_mei_init); +module_exit(ipts_mei_exit); diff --git a/drivers/misc/ipts/protocol.h b/drivers/misc/ipts/protocol.h new file mode 100644 index 0000000000000..c3458904a94da --- /dev/null +++ b/drivers/misc/ipts/protocol.h @@ -0,0 +1,347 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef _IPTS_PROTOCOL_H_ +#define _IPTS_PROTOCOL_H_ + +#include + +/* + * The MEI client ID for IPTS functionality. + */ +#define IPTS_MEI_UUID \ + UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, \ + 0x02, 0xae, 0x04) + +/* + * Queries the device for vendor specific information. + * + * The command must not contain any payload. + * The response will contain struct ipts_get_device_info_rsp as payload. + */ +#define IPTS_CMD_GET_DEVICE_INFO 0x00000001 +#define IPTS_RSP_GET_DEVICE_INFO 0x80000001 + +/* + * Sets the mode that IPTS will operate in. + * + * The command must contain struct ipts_set_mode_cmd as payload. + * The response will not contain any payload. + */ +#define IPTS_CMD_SET_MODE 0x00000002 +#define IPTS_RSP_SET_MODE 0x80000002 + +/* + * Configures the memory buffers that the ME will use + * for passing data to the host. + * + * The command must contain struct ipts_set_mem_window_cmd as payload. + * The response will not contain any payload. + */ +#define IPTS_CMD_SET_MEM_WINDOW 0x00000003 +#define IPTS_RSP_SET_MEM_WINDOW 0x80000003 + +/* + * Signals that the host is ready to receive data to the ME. + * + * The command must not contain any payload. + * The response will not contain any payload. + */ +#define IPTS_CMD_READY_FOR_DATA 0x00000005 +#define IPTS_RSP_READY_FOR_DATA 0x80000005 + +/* + * Signals that a buffer can be refilled to the ME. + * + * The command must contain struct ipts_feedback_cmd as payload. + * The response will not contain any payload. + */ +#define IPTS_CMD_FEEDBACK 0x00000006 +#define IPTS_RSP_FEEDBACK 0x80000006 + +/* + * Resets the data flow from the ME to the hosts and + * clears the buffers that were set with SET_MEM_WINDOW. + * + * The command must not contain any payload. + * The response will not contain any payload. + */ +#define IPTS_CMD_CLEAR_MEM_WINDOW 0x00000007 +#define IPTS_RSP_CLEAR_MEM_WINDOW 0x80000007 + +/* + * Instructs the ME to reset the touch sensor. + * + * The command must contain struct ipts_reset_sensor_cmd as payload. + * The response will not contain any payload. + */ +#define IPTS_CMD_RESET_SENSOR 0x0000000B +#define IPTS_RSP_RESET_SENSOR 0x8000000B + +/** + * enum ipts_status - Possible status codes returned by IPTS commands. + * @IPTS_STATUS_SUCCESS: Operation completed successfully. + * @IPTS_STATUS_INVALID_PARAMS: Command contained a payload with invalid parameters. + * @IPTS_STATUS_ACCESS_DENIED: ME could not validate buffer addresses supplied by host. + * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. + * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. + * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. + * The host must wait for a response before sending another + * command of the same type. + * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. Either no sensor is connected, it + * has not been initialized yet, or the system is improperly + * configured. + * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. + * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. + * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. + * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. + * The host can ignore this error and attempt to continue. + * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by ME or host. + * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. + * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. + * @IPTS_STATUS_TIMEOUT: The operation timed out. + * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. + * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported a fatal error during reset sequence. + * Further progress is not possible. + * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported a fatal error during reset sequence. + * The host can attempt to continue. + * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. + * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. + */ +enum ipts_status { + IPTS_STATUS_SUCCESS = 0, + IPTS_STATUS_INVALID_PARAMS = 1, + IPTS_STATUS_ACCESS_DENIED = 2, + IPTS_STATUS_CMD_SIZE_ERROR = 3, + IPTS_STATUS_NOT_READY = 4, + IPTS_STATUS_REQUEST_OUTSTANDING = 5, + IPTS_STATUS_NO_SENSOR_FOUND = 6, + IPTS_STATUS_OUT_OF_MEMORY = 7, + IPTS_STATUS_INTERNAL_ERROR = 8, + IPTS_STATUS_SENSOR_DISABLED = 9, + IPTS_STATUS_COMPAT_CHECK_FAIL = 10, + IPTS_STATUS_SENSOR_EXPECTED_RESET = 11, + IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 12, + IPTS_STATUS_RESET_FAILED = 13, + IPTS_STATUS_TIMEOUT = 14, + IPTS_STATUS_TEST_MODE_FAIL = 15, + IPTS_STATUS_SENSOR_FAIL_FATAL = 16, + IPTS_STATUS_SENSOR_FAIL_NONFATAL = 17, + IPTS_STATUS_INVALID_DEVICE_CAPS = 18, + IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 19, +}; + +/* + * The amount of buffers that is used for IPTS + */ +#define IPTS_BUFFERS 16 + +/* + * The special buffer ID that is used for direct host2me feedback. + */ +#define IPTS_HOST2ME_BUFFER IPTS_BUFFERS + +/** + * enum ipts_mode - Operation mode for IPTS hardware + * @IPTS_MODE_SINGLETOUCH: Fallback that supports only one finger and no stylus. + * The data is received as a HID report with ID 64. + * @IPTS_MODE_MULTITOUCH: The "proper" operation mode for IPTS. It will return + * stylus data as well as capacitive heatmap touch data. + * This data needs to be processed in userspace. + */ +enum ipts_mode { + IPTS_MODE_SINGLETOUCH = 0, + IPTS_MODE_MULTITOUCH = 1, +}; + +/** + * struct ipts_set_mode_cmd - Payload for the SET_MODE command. + * @mode: The mode that IPTS should operate in. + */ +struct ipts_set_mode_cmd { + enum ipts_mode mode; + u8 reserved[12]; +} __packed; + +#define IPTS_WORKQUEUE_SIZE 8192 +#define IPTS_WORKQUEUE_ITEM_SIZE 16 + +/** + * struct ipts_set_mem_window_cmd - Payload for the SET_MEM_WINDOW command. + * @data_buffer_addr_lower: Lower 32 bits of the data buffer addresses. + * @data_buffer_addr_upper: Upper 32 bits of the data buffer addresses. + * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. + * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. + * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. + * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. + * @feedback_buffer_addr_lower: Lower 32 bits of the feedback buffer addresses. + * @feedback_buffer_addr_upper: Upper 32 bits of the feedback buffer addresses. + * @host2me_addr_lower: Lower 32 bits of the host2me buffer address. + * @host2me_addr_upper: Upper 32 bits of the host2me buffer address. + * @workqueue_item_size: Magic value. (IPTS_WORKQUEUE_ITEM_SIZE) + * @workqueue_size: Magic value. (IPTS_WORKQUEUE_SIZE) + * + * The data buffers are buffers that get filled with touch data by the ME. + * The doorbell buffer is a u32 that gets incremented by the ME once a data + * buffer has been filled with new data. + * + * The other buffers are required for using GuC submission with binary + * firmware. Since support for GuC submission has been dropped from i915, + * they are not used anymore, but they need to be allocated and passed, + * otherwise the hardware will refuse to start. + */ +struct ipts_set_mem_window_cmd { + u32 data_buffer_addr_lower[IPTS_BUFFERS]; + u32 data_buffer_addr_upper[IPTS_BUFFERS]; + u32 workqueue_addr_lower; + u32 workqueue_addr_upper; + u32 doorbell_addr_lower; + u32 doorbell_addr_upper; + u32 feedback_buffer_addr_lower[IPTS_BUFFERS]; + u32 feedback_buffer_addr_upper[IPTS_BUFFERS]; + u32 host2me_addr_lower; + u32 host2me_addr_upper; + u32 host2me_size; + u8 reserved1; + u8 workqueue_item_size; + u16 workqueue_size; + u8 reserved[32]; +} __packed; + +/** + * struct ipts_feedback_cmd - Payload for the FEEDBACK command. + * @buffer: The buffer that the ME should refill. + */ +struct ipts_feedback_cmd { + u32 buffer; + u8 reserved[12]; +} __packed; + +/** + * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. + */ +enum ipts_feedback_cmd_type { + IPTS_FEEDBACK_CMD_TYPE_NONE = 0, + IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, + IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, + IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, + IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, + IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, + IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, +}; + +/** + * enum ipts_feedback_data_type - Describes the data that a feedback buffer contains. + * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. + * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features command. + * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features command. + * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. + * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. + */ +enum ipts_feedback_data_type { + IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, + IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, + IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, + IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, + IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, +}; + +/** + * struct ipts_feedback_buffer - The contents of an IPTS feedback buffer. + * @cmd_type: A command that should be executed on the sensor. + * @size: The size of the payload to be written. + * @buffer: The ID of the buffer that contains this feedback data. + * @protocol: The protocol version of the EDS. + * @data_type: The type of payload that the buffer contains. + * @spi_offset: The offset at which to write the payload data. + * @payload: Payload for the feedback command, or 0 if no payload is sent. + */ +struct ipts_feedback_buffer { + enum ipts_feedback_cmd_type cmd_type; + u32 size; + u32 buffer; + u32 protocol; + enum ipts_feedback_data_type data_type; + u32 spi_offset; + u8 reserved[40]; + u8 payload[]; +} __packed; + +/** + * enum ipts_reset_type - Possible ways of resetting the touch sensor + * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. + * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI interface. + */ +enum ipts_reset_type { + IPTS_RESET_TYPE_HARD = 0, + IPTS_RESET_TYPE_SOFT = 1, +}; + +/** + * struct ipts_reset_sensor_cmd - Payload for the RESET_SENSOR command. + * @type: What type of reset should be performed. + */ +struct ipts_reset_sensor_cmd { + enum ipts_reset_type type; + u8 reserved[4]; +} __packed; + +/** + * struct ipts_command - A message sent from the host to the ME. + * @code: The message code describing the command. (see IPTS_CMD_*) + * @payload: Payload for the command, or 0 if no payload is required. + */ +struct ipts_command { + u32 code; + u8 payload[320]; +} __packed; + +/** + * struct ipts_device_info - Payload for the GET_DEVICE_INFO response. + * @vendor_id: Vendor ID of the touch sensor. + * @device_id: Device ID of the touch sensor. + * @hw_rev: Hardware revision of the touch sensor. + * @fw_rev: Firmware revision of the touch sensor. + * @data_size: Required size of one data buffer. + * @feedback_size: Required size of one feedback buffer. + * @mode: Current operation mode of IPTS. + * @max_contacts: The amount of concurrent touches supported by the sensor. + */ +struct ipts_get_device_info_rsp { + u16 vendor_id; + u16 device_id; + u32 hw_rev; + u32 fw_rev; + u32 data_size; + u32 feedback_size; + enum ipts_mode mode; + u8 max_contacts; + u8 reserved[19]; +} __packed; + +/** + * struct ipts_feedback_rsp - Payload for the FEEDBACK response. + * @buffer: The buffer that has received feedback. + */ +struct ipts_feedback_rsp { + u32 buffer; +} __packed; + +/** + * struct ipts_response - A message sent from the ME to the host. + * @code: The message code describing the response. (see IPTS_RSP_*) + * @status: The status code returned by the command. + * @payload: Payload returned by the command. + */ +struct ipts_response { + u32 code; + enum ipts_status status; + u8 payload[80]; +} __packed; + +#endif /* _IPTS_PROTOCOL_H_ */ diff --git a/drivers/misc/ipts/receiver.c b/drivers/misc/ipts/receiver.c new file mode 100644 index 0000000000000..23dca13c21391 --- /dev/null +++ b/drivers/misc/ipts/receiver.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include + +#include "context.h" +#include "control.h" +#include "protocol.h" +#include "resources.h" + +/* + * Temporary parameter to guard gen7 multitouch mode. + * Remove once gen7 has stable iptsd support. + */ +static bool gen7mt; +module_param(gen7mt, bool, 0644); + +static int ipts_receiver_handle_get_device_info(struct ipts_context *ipts, + struct ipts_response *rsp) +{ + struct ipts_set_mode_cmd cmd; + + memcpy(&ipts->device_info, rsp->payload, + sizeof(struct ipts_get_device_info_rsp)); + + memset(&cmd, 0, sizeof(struct ipts_set_mode_cmd)); + cmd.mode = IPTS_MODE_MULTITOUCH; + + return ipts_control_send(ipts, IPTS_CMD_SET_MODE, &cmd, + sizeof(struct ipts_set_mode_cmd)); +} + +static int ipts_receiver_handle_set_mode(struct ipts_context *ipts) +{ + int i, ret; + struct ipts_set_mem_window_cmd cmd; + + ret = ipts_resources_alloc(ipts); + if (ret) { + dev_err(ipts->dev, "Failed to allocate resources\n"); + return ret; + } + + memset(&cmd, 0, sizeof(struct ipts_set_mem_window_cmd)); + + for (i = 0; i < IPTS_BUFFERS; i++) { + cmd.data_buffer_addr_lower[i] = + lower_32_bits(ipts->data[i].dma_address); + + cmd.data_buffer_addr_upper[i] = + upper_32_bits(ipts->data[i].dma_address); + + cmd.feedback_buffer_addr_lower[i] = + lower_32_bits(ipts->feedback[i].dma_address); + + cmd.feedback_buffer_addr_upper[i] = + upper_32_bits(ipts->feedback[i].dma_address); + } + + cmd.workqueue_addr_lower = lower_32_bits(ipts->workqueue.dma_address); + cmd.workqueue_addr_upper = upper_32_bits(ipts->workqueue.dma_address); + + cmd.doorbell_addr_lower = lower_32_bits(ipts->doorbell.dma_address); + cmd.doorbell_addr_upper = upper_32_bits(ipts->doorbell.dma_address); + + cmd.host2me_addr_lower = lower_32_bits(ipts->host2me.dma_address); + cmd.host2me_addr_upper = upper_32_bits(ipts->host2me.dma_address); + + cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; + cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; + + return ipts_control_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, + sizeof(struct ipts_set_mem_window_cmd)); +} + +static int ipts_receiver_handle_set_mem_window(struct ipts_context *ipts) +{ + int ret; + + dev_info(ipts->dev, "Device %04hX:%04hX ready\n", + ipts->device_info.vendor_id, ipts->device_info.device_id); + ipts->status = IPTS_HOST_STATUS_STARTED; + + ret = ipts_control_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); + if (ret) + return ret; + + if (!gen7mt) + return 0; + + return ipts_control_set_feature(ipts, 0x5, 0x1); +} + +static int ipts_receiver_handle_feedback(struct ipts_context *ipts, + struct ipts_response *rsp) +{ + struct ipts_feedback_rsp feedback; + + if (ipts->status != IPTS_HOST_STATUS_STOPPING) + return 0; + + memcpy(&feedback, rsp->payload, sizeof(feedback)); + + if (feedback.buffer < IPTS_BUFFERS - 1) + return ipts_control_send_feedback(ipts, feedback.buffer + 1); + + return ipts_control_send(ipts, IPTS_CMD_CLEAR_MEM_WINDOW, NULL, 0); +} + +static int ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts) +{ + ipts->status = IPTS_HOST_STATUS_STOPPED; + + if (ipts->restart) + return ipts_control_start(ipts); + + return 0; +} + +static bool ipts_receiver_sensor_was_reset(u32 status) +{ + return status == IPTS_STATUS_SENSOR_EXPECTED_RESET || + status == IPTS_STATUS_SENSOR_UNEXPECTED_RESET; +} + +static bool ipts_receiver_handle_error(struct ipts_context *ipts, + struct ipts_response *rsp) +{ + bool error; + + switch (rsp->status) { + case IPTS_STATUS_SUCCESS: + case IPTS_STATUS_COMPAT_CHECK_FAIL: + error = false; + break; + case IPTS_STATUS_INVALID_PARAMS: + error = rsp->code != IPTS_RSP_FEEDBACK; + break; + case IPTS_STATUS_SENSOR_DISABLED: + error = ipts->status != IPTS_HOST_STATUS_STOPPING; + break; + default: + error = true; + break; + } + + if (!error) + return false; + + dev_err(ipts->dev, "Command 0x%08x failed: %d\n", rsp->code, + rsp->status); + + if (ipts_receiver_sensor_was_reset(rsp->status)) { + dev_err(ipts->dev, "Sensor was reset\n"); + + if (ipts_control_restart(ipts)) + dev_err(ipts->dev, "Failed to restart IPTS\n"); + } + + return true; +} + +static void ipts_receiver_handle_response(struct ipts_context *ipts, + struct ipts_response *rsp) +{ + int ret; + + if (ipts_receiver_handle_error(ipts, rsp)) + return; + + switch (rsp->code) { + case IPTS_RSP_GET_DEVICE_INFO: + ret = ipts_receiver_handle_get_device_info(ipts, rsp); + break; + case IPTS_RSP_SET_MODE: + ret = ipts_receiver_handle_set_mode(ipts); + break; + case IPTS_RSP_SET_MEM_WINDOW: + ret = ipts_receiver_handle_set_mem_window(ipts); + break; + case IPTS_RSP_FEEDBACK: + ret = ipts_receiver_handle_feedback(ipts, rsp); + break; + case IPTS_RSP_CLEAR_MEM_WINDOW: + ret = ipts_receiver_handle_clear_mem_window(ipts); + break; + default: + ret = 0; + break; + } + + if (!ret) + return; + + dev_err(ipts->dev, "Error while handling response 0x%08x: %d\n", + rsp->code, ret); + + if (ipts_control_stop(ipts)) + dev_err(ipts->dev, "Failed to stop IPTS\n"); +} + +void ipts_receiver_callback(struct mei_cl_device *cldev) +{ + int ret; + struct ipts_response rsp; + struct ipts_context *ipts; + + ipts = mei_cldev_get_drvdata(cldev); + + ret = mei_cldev_recv(cldev, (u8 *)&rsp, sizeof(struct ipts_response)); + if (ret <= 0) { + dev_err(ipts->dev, "Error while reading response: %d\n", ret); + return; + } + + ipts_receiver_handle_response(ipts, &rsp); +} diff --git a/drivers/misc/ipts/receiver.h b/drivers/misc/ipts/receiver.h new file mode 100644 index 0000000000000..7f075afa7ef86 --- /dev/null +++ b/drivers/misc/ipts/receiver.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef _IPTS_RECEIVER_H_ +#define _IPTS_RECEIVER_H_ + +#include + +void ipts_receiver_callback(struct mei_cl_device *cldev); + +#endif /* _IPTS_RECEIVER_H_ */ diff --git a/drivers/misc/ipts/resources.c b/drivers/misc/ipts/resources.c new file mode 100644 index 0000000000000..8e3a2409e4383 --- /dev/null +++ b/drivers/misc/ipts/resources.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include + +#include "context.h" + +void ipts_resources_free(struct ipts_context *ipts) +{ + int i; + struct ipts_buffer_info *buffers; + + u32 data_buffer_size = ipts->device_info.data_size; + u32 feedback_buffer_size = ipts->device_info.feedback_size; + + buffers = ipts->data; + for (i = 0; i < IPTS_BUFFERS; i++) { + if (!buffers[i].address) + continue; + + dma_free_coherent(ipts->dev, data_buffer_size, + buffers[i].address, buffers[i].dma_address); + + buffers[i].address = NULL; + buffers[i].dma_address = 0; + } + + buffers = ipts->feedback; + for (i = 0; i < IPTS_BUFFERS; i++) { + if (!buffers[i].address) + continue; + + dma_free_coherent(ipts->dev, feedback_buffer_size, + buffers[i].address, buffers[i].dma_address); + + buffers[i].address = NULL; + buffers[i].dma_address = 0; + } + + if (ipts->doorbell.address) { + dma_free_coherent(ipts->dev, sizeof(u32), + ipts->doorbell.address, + ipts->doorbell.dma_address); + + ipts->doorbell.address = NULL; + ipts->doorbell.dma_address = 0; + } + + if (ipts->workqueue.address) { + dma_free_coherent(ipts->dev, sizeof(u32), + ipts->workqueue.address, + ipts->workqueue.dma_address); + + ipts->workqueue.address = NULL; + ipts->workqueue.dma_address = 0; + } + + if (ipts->host2me.address) { + dma_free_coherent(ipts->dev, feedback_buffer_size, + ipts->host2me.address, + ipts->host2me.dma_address); + + ipts->host2me.address = NULL; + ipts->host2me.dma_address = 0; + } +} + +int ipts_resources_alloc(struct ipts_context *ipts) +{ + int i; + struct ipts_buffer_info *buffers; + + u32 data_buffer_size = ipts->device_info.data_size; + u32 feedback_buffer_size = ipts->device_info.feedback_size; + + buffers = ipts->data; + for (i = 0; i < IPTS_BUFFERS; i++) { + buffers[i].address = + dma_alloc_coherent(ipts->dev, data_buffer_size, + &buffers[i].dma_address, GFP_KERNEL); + + if (!buffers[i].address) + goto release_resources; + } + + buffers = ipts->feedback; + for (i = 0; i < IPTS_BUFFERS; i++) { + buffers[i].address = + dma_alloc_coherent(ipts->dev, feedback_buffer_size, + &buffers[i].dma_address, GFP_KERNEL); + + if (!buffers[i].address) + goto release_resources; + } + + ipts->doorbell.address = + dma_alloc_coherent(ipts->dev, sizeof(u32), + &ipts->doorbell.dma_address, GFP_KERNEL); + + if (!ipts->doorbell.address) + goto release_resources; + + ipts->workqueue.address = + dma_alloc_coherent(ipts->dev, sizeof(u32), + &ipts->workqueue.dma_address, GFP_KERNEL); + + if (!ipts->workqueue.address) + goto release_resources; + + ipts->host2me.address = + dma_alloc_coherent(ipts->dev, feedback_buffer_size, + &ipts->host2me.dma_address, GFP_KERNEL); + + if (!ipts->workqueue.address) + goto release_resources; + + return 0; + +release_resources: + + ipts_resources_free(ipts); + return -ENOMEM; +} diff --git a/drivers/misc/ipts/resources.h b/drivers/misc/ipts/resources.h new file mode 100644 index 0000000000000..fdac0eee91562 --- /dev/null +++ b/drivers/misc/ipts/resources.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef _IPTS_RESOURCES_H_ +#define _IPTS_RESOURCES_H_ + +#include "context.h" + +int ipts_resources_alloc(struct ipts_context *ipts); +void ipts_resources_free(struct ipts_context *ipts); + +#endif /* _IPTS_RESOURCES_H_ */ diff --git a/drivers/misc/ipts/uapi.c b/drivers/misc/ipts/uapi.c new file mode 100644 index 0000000000000..598f0710ad64a --- /dev/null +++ b/drivers/misc/ipts/uapi.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#include +#include +#include +#include +#include +#include + +#include "context.h" +#include "control.h" +#include "protocol.h" +#include "uapi.h" + +struct ipts_uapi uapi; + +static ssize_t ipts_uapi_read(struct file *file, char __user *buf, size_t count, + loff_t *offset) +{ + int buffer; + int maxbytes; + struct ipts_context *ipts = uapi.ipts; + + buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); + + if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) + return -ENODEV; + + maxbytes = ipts->device_info.data_size - *offset; + if (maxbytes <= 0 || count > maxbytes) + return -EINVAL; + + if (copy_to_user(buf, ipts->data[buffer].address + *offset, count)) + return -EFAULT; + + return count; +} + +static long ipts_uapi_ioctl_get_device_ready(struct ipts_context *ipts, + unsigned long arg) +{ + void __user *buffer = (void __user *)arg; + u8 ready = 0; + + if (ipts) + ready = ipts->status == IPTS_HOST_STATUS_STARTED; + + if (copy_to_user(buffer, &ready, sizeof(u8))) + return -EFAULT; + + return 0; +} + +static long ipts_uapi_ioctl_get_device_info(struct ipts_context *ipts, + unsigned long arg) +{ + struct ipts_device_info info; + void __user *buffer = (void __user *)arg; + + if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) + return -ENODEV; + + info.vendor = ipts->device_info.vendor_id; + info.product = ipts->device_info.device_id; + info.version = ipts->device_info.fw_rev; + info.buffer_size = ipts->device_info.data_size; + info.max_contacts = ipts->device_info.max_contacts; + + if (copy_to_user(buffer, &info, sizeof(struct ipts_device_info))) + return -EFAULT; + + return 0; +} + +static long ipts_uapi_ioctl_get_doorbell(struct ipts_context *ipts, + unsigned long arg) +{ + void __user *buffer = (void __user *)arg; + + if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) + return -ENODEV; + + if (copy_to_user(buffer, ipts->doorbell.address, sizeof(u32))) + return -EFAULT; + + return 0; +} + +static long ipts_uapi_ioctl_send_feedback(struct ipts_context *ipts, + struct file *file) +{ + int ret; + u32 buffer; + + if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) + return -ENODEV; + + buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); + + ret = ipts_control_send_feedback(ipts, buffer); + if (ret) + return -EFAULT; + + return 0; +} + +static long ipts_uapi_ioctl_send_reset(struct ipts_context *ipts) +{ + int ret; + struct ipts_reset_sensor_cmd cmd; + + if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) + return -ENODEV; + + memset(&cmd, 0, sizeof(struct ipts_reset_sensor_cmd)); + cmd.type = IPTS_RESET_TYPE_SOFT; + + ret = ipts_control_send(ipts, IPTS_CMD_RESET_SENSOR, &cmd, + sizeof(struct ipts_reset_sensor_cmd)); + + if (ret) + return -EFAULT; + + return 0; +} + +static long ipts_uapi_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct ipts_context *ipts = uapi.ipts; + + switch (cmd) { + case IPTS_IOCTL_GET_DEVICE_READY: + return ipts_uapi_ioctl_get_device_ready(ipts, arg); + case IPTS_IOCTL_GET_DEVICE_INFO: + return ipts_uapi_ioctl_get_device_info(ipts, arg); + case IPTS_IOCTL_GET_DOORBELL: + return ipts_uapi_ioctl_get_doorbell(ipts, arg); + case IPTS_IOCTL_SEND_FEEDBACK: + return ipts_uapi_ioctl_send_feedback(ipts, file); + case IPTS_IOCTL_SEND_RESET: + return ipts_uapi_ioctl_send_reset(ipts); + default: + return -ENOTTY; + } +} + +static const struct file_operations ipts_uapi_fops = { + .owner = THIS_MODULE, + .read = ipts_uapi_read, + .unlocked_ioctl = ipts_uapi_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = ipts_uapi_ioctl, +#endif +}; + +void ipts_uapi_link(struct ipts_context *ipts) +{ + uapi.ipts = ipts; +} + +void ipts_uapi_unlink(void) +{ + uapi.ipts = NULL; +} + +int ipts_uapi_init(void) +{ + int i, major; + + alloc_chrdev_region(&uapi.dev, 0, IPTS_BUFFERS, "ipts"); + uapi.class = class_create(THIS_MODULE, "ipts"); + + major = MAJOR(uapi.dev); + + cdev_init(&uapi.cdev, &ipts_uapi_fops); + uapi.cdev.owner = THIS_MODULE; + cdev_add(&uapi.cdev, MKDEV(major, 0), IPTS_BUFFERS); + + for (i = 0; i < IPTS_BUFFERS; i++) { + device_create(uapi.class, NULL, MKDEV(major, i), NULL, + "ipts/%d", i); + } + + return 0; +} + +void ipts_uapi_free(void) +{ + int i; + int major; + + major = MAJOR(uapi.dev); + + for (i = 0; i < IPTS_BUFFERS; i++) + device_destroy(uapi.class, MKDEV(major, i)); + + cdev_del(&uapi.cdev); + + unregister_chrdev_region(MKDEV(major, 0), MINORMASK); + class_destroy(uapi.class); +} diff --git a/drivers/misc/ipts/uapi.h b/drivers/misc/ipts/uapi.h new file mode 100644 index 0000000000000..53fb86a88f978 --- /dev/null +++ b/drivers/misc/ipts/uapi.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2020 Dorian Stoll + * + * Linux driver for Intel Precise Touch & Stylus + */ + +#ifndef _IPTS_UAPI_H_ +#define _IPTS_UAPI_H_ + +#include + +#include "context.h" + +struct ipts_uapi { + dev_t dev; + struct class *class; + struct cdev cdev; + + struct ipts_context *ipts; +}; + +struct ipts_device_info { + __u16 vendor; + __u16 product; + __u32 version; + __u32 buffer_size; + __u8 max_contacts; + + /* For future expansion */ + __u8 reserved[19]; +}; + +#define IPTS_IOCTL_GET_DEVICE_READY _IOR(0x86, 0x01, __u8) +#define IPTS_IOCTL_GET_DEVICE_INFO _IOR(0x86, 0x02, struct ipts_device_info) +#define IPTS_IOCTL_GET_DOORBELL _IOR(0x86, 0x03, __u32) +#define IPTS_IOCTL_SEND_FEEDBACK _IO(0x86, 0x04) +#define IPTS_IOCTL_SEND_RESET _IO(0x86, 0x05) + +void ipts_uapi_link(struct ipts_context *ipts); +void ipts_uapi_unlink(void); + +int ipts_uapi_init(void); +void ipts_uapi_free(void); + +#endif /* _IPTS_UAPI_H_ */ diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h index 15e8e2b322b1a..91587b8083236 100644 --- a/drivers/misc/mei/hw-me-regs.h +++ b/drivers/misc/mei/hw-me-regs.h @@ -92,6 +92,7 @@ #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ +#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ #define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 5435604327a71..1165ee4f5928b 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -97,6 +97,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c index 9e1f483e1362a..34cfce241e4a5 100644 --- a/drivers/net/wireless/ath/ath10k/core.c +++ b/drivers/net/wireless/ath/ath10k/core.c @@ -36,6 +36,9 @@ static bool skip_otp; static bool rawmode; static bool fw_diag_log; +static char *override_board = ""; +static char *override_board2 = ""; + unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); @@ -48,6 +51,9 @@ module_param(rawmode, bool, 0644); module_param(fw_diag_log, bool, 0644); module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); +module_param(override_board, charp, 0644); +module_param(override_board2, charp, 0644); + MODULE_PARM_DESC(debug_mask, "Debugging mask"); MODULE_PARM_DESC(uart_print, "Uart target debugging"); MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); @@ -56,6 +62,9 @@ MODULE_PARM_DESC(rawmode, "Use raw 802.11 frame datapath"); MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); +MODULE_PARM_DESC(override_board, "Override for board.bin file"); +MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); + static const struct ath10k_hw_params ath10k_hw_params_list[] = { { .id = QCA988X_HW_2_0_VERSION, @@ -860,6 +869,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) return 0; } +static const char *ath10k_override_board_fw_file(struct ath10k *ar, + const char *file) +{ + if (strcmp(file, "board.bin") == 0) { + if (strcmp(override_board, "") == 0) + return file; + + if (strcmp(override_board, "none") == 0) { + dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); + return NULL; + } + + dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", + override_board); + + return override_board; + } + + if (strcmp(file, "board-2.bin") == 0) { + if (strcmp(override_board2, "") == 0) + return file; + + if (strcmp(override_board2, "none") == 0) { + dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); + return NULL; + } + + dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", + override_board2); + + return override_board2; + } + + return file; +} + static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, const char *dir, const char *file) @@ -874,6 +919,19 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, if (dir == NULL) dir = "."; + /* HACK: Override board.bin and board-2.bin files if specified. + * + * Some Surface devices perform better with a different board + * configuration. To this end, one would need to replace the board.bin + * file with the modified config and remove the board-2.bin file. + * Unfortunately, that's not a solution that we can easily package. So + * we add module options to perform these overrides here. + */ + + file = ath10k_override_board_fw_file(ar, file); + if (!file) + return ERR_PTR(-ENOENT); + snprintf(filename, sizeof(filename), "%s/%s", dir, file); ret = firmware_request_nowarn(&fw, filename, ar->dev); ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot fw request '%s': %d\n", diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c index d5fb29400bad5..b4ad0113a0354 100644 --- a/drivers/net/wireless/marvell/mwifiex/pcie.c +++ b/drivers/net/wireless/marvell/mwifiex/pcie.c @@ -238,6 +238,12 @@ static int mwifiex_write_reg(struct mwifiex_adapter *adapter, int reg, u32 data) iowrite32(data, card->pci_mmap1 + reg); + /* Do a read-back, which makes the write non-posted, ensuring the + * completion before returning. + * The firmware of the 88W8897 card is buggy and this avoids crashes. + */ + ioread32(card->pci_mmap1 + reg); + return 0; } @@ -380,6 +386,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { struct pcie_service_card *card; + struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); int ret; pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", @@ -421,6 +428,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, return -1; } + /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing + * after suspend + */ + if (card->quirks & QUIRK_NO_BRIDGE_D3) + parent_pdev->bridge_d3 = false; + return 0; } @@ -1774,9 +1787,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) static int mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) { struct pcie_service_card *card = adapter->card; + struct pci_dev *pdev = card->dev; + struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; + /* Trigger a function level reset of the PCI bridge device, this makes + * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value + * that prevents the system from entering package C10 and S0ix powersaving + * states. + * We need to do it here because it must happen after firmware + * initialization and this function is called after that is done. + */ + if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) + pci_reset_function(parent_pdev); + /* Write the RX ring read pointer in to reg->rx_rdptr */ if (mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | tx_wrap)) { @@ -2993,6 +3018,16 @@ static void mwifiex_pcie_card_reset_work(struct mwifiex_adapter *adapter) { struct pcie_service_card *card = adapter->card; + /* On Surface 3, reset_wsid method removes then re-probes card by + * itself. So, need to place it here and skip performing any other + * reset-related works. + */ + if (card->quirks & QUIRK_FW_RST_WSID_S3) { + mwifiex_pcie_reset_wsid_quirk(card->dev); + /* skip performing any other reset-related works */ + return; + } + /* We can't afford to wait here; remove() might be waiting on us. If we * can't grab the device lock, maybe we'll get another chance later. */ diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c index 0234cf3c2974f..6437f067d07a4 100644 --- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c @@ -15,10 +15,21 @@ * this warranty disclaimer. */ +#include #include #include "pcie_quirks.h" +/* For reset_wsid quirk */ +#define ACPI_WSID_PATH "\\_SB.WSID" +#define WSID_REV 0x0 +#define WSID_FUNC_WIFI_PWR_OFF 0x1 +#define WSID_FUNC_WIFI_PWR_ON 0x2 +/* WSID _DSM UUID: "534ea3bf-fcc2-4e7a-908f-a13978f0c7ef" */ +static const guid_t wsid_dsm_guid = + GUID_INIT(0x534ea3bf, 0xfcc2, 0x4e7a, + 0x90, 0x8f, 0xa1, 0x39, 0x78, 0xf0, 0xc7, 0xef); + /* quirk table based on DMI matching */ static const struct dmi_system_id mwifiex_quirk_table[] = { { @@ -27,7 +38,9 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | + QUIRK_NO_BRIDGE_D3 | + QUIRK_DO_FLR_ON_BRIDGE), }, { .ident = "Surface Pro 5", @@ -36,7 +49,9 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | + QUIRK_NO_BRIDGE_D3 | + QUIRK_DO_FLR_ON_BRIDGE), }, { .ident = "Surface Pro 5 (LTE)", @@ -45,7 +60,9 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | + QUIRK_NO_BRIDGE_D3 | + QUIRK_DO_FLR_ON_BRIDGE), }, { .ident = "Surface Pro 6", @@ -53,7 +70,9 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | + QUIRK_NO_BRIDGE_D3 | + QUIRK_DO_FLR_ON_BRIDGE), }, { .ident = "Surface Book 1", @@ -61,7 +80,9 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | + QUIRK_NO_BRIDGE_D3 | + QUIRK_DO_FLR_ON_BRIDGE), }, { .ident = "Surface Book 2", @@ -69,7 +90,9 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | + QUIRK_NO_BRIDGE_D3 | + QUIRK_DO_FLR_ON_BRIDGE), }, { .ident = "Surface Laptop 1", @@ -77,7 +100,9 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | + QUIRK_NO_BRIDGE_D3 | + QUIRK_DO_FLR_ON_BRIDGE), }, { .ident = "Surface Laptop 2", @@ -85,7 +110,26 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | + QUIRK_NO_BRIDGE_D3 | + QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface 3", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + .driver_data = (void *)QUIRK_FW_RST_WSID_S3, + }, + { + .ident = "Surface 3", + .matches = { + DMI_EXACT_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OEMB"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OEMB"), + }, + .driver_data = (void *)QUIRK_FW_RST_WSID_S3, }, {} }; @@ -103,6 +147,14 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) dev_info(&pdev->dev, "no quirks enabled\n"); if (card->quirks & QUIRK_FW_RST_D3COLD) dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); + if (card->quirks & QUIRK_FW_RST_WSID_S3) + dev_info(&pdev->dev, + "quirk reset_wsid for Surface 3 enabled\n"); + if (card->quirks & QUIRK_NO_BRIDGE_D3) + dev_info(&pdev->dev, + "quirk no_brigde_d3 enabled\n"); + if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) + dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); } static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) @@ -159,3 +211,64 @@ int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) return 0; } + +int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev) +{ + acpi_handle handle; + union acpi_object *obj; + acpi_status status; + + dev_info(&pdev->dev, "Using reset_wsid quirk to perform FW reset\n"); + + status = acpi_get_handle(NULL, ACPI_WSID_PATH, &handle); + if (ACPI_FAILURE(status)) { + dev_err(&pdev->dev, "No ACPI handle for path %s\n", + ACPI_WSID_PATH); + return -ENODEV; + } + + if (!acpi_has_method(handle, "_DSM")) { + dev_err(&pdev->dev, "_DSM method not found\n"); + return -ENODEV; + } + + if (!acpi_check_dsm(handle, &wsid_dsm_guid, + WSID_REV, WSID_FUNC_WIFI_PWR_OFF)) { + dev_err(&pdev->dev, + "_DSM method doesn't support wifi power off func\n"); + return -ENODEV; + } + + if (!acpi_check_dsm(handle, &wsid_dsm_guid, + WSID_REV, WSID_FUNC_WIFI_PWR_ON)) { + dev_err(&pdev->dev, + "_DSM method doesn't support wifi power on func\n"); + return -ENODEV; + } + + /* card will be removed immediately after this call on Surface 3 */ + dev_info(&pdev->dev, "turning wifi off...\n"); + obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, + WSID_REV, WSID_FUNC_WIFI_PWR_OFF, + NULL); + if (!obj) { + dev_err(&pdev->dev, + "device _DSM execution failed for turning wifi off\n"); + return -EIO; + } + ACPI_FREE(obj); + + /* card will be re-probed immediately after this call on Surface 3 */ + dev_info(&pdev->dev, "turning wifi on...\n"); + obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, + WSID_REV, WSID_FUNC_WIFI_PWR_ON, + NULL); + if (!obj) { + dev_err(&pdev->dev, + "device _DSM execution failed for turning wifi on\n"); + return -EIO; + } + ACPI_FREE(obj); + + return 0; +} diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h index 8ec4176d698f4..0e429779bb04e 100644 --- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h @@ -19,5 +19,13 @@ #define QUIRK_FW_RST_D3COLD BIT(0) +/* Surface 3 and Surface Pro 3 have the same _DSM method but need to + * be handled differently. Currently, only S3 is supported. + */ +#define QUIRK_FW_RST_WSID_S3 BIT(1) +#define QUIRK_NO_BRIDGE_D3 BIT(2) +#define QUIRK_DO_FLR_ON_BRIDGE BIT(3) + void mwifiex_initialize_quirks(struct pcie_service_card *card); int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev); diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig index eb79fbed8059a..68656e8f309ed 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig @@ -72,18 +72,45 @@ config SURFACE_AGGREGATOR_CDEV The provided interface is intended for debugging and development only, and should not be used otherwise. +config SURFACE_AGGREGATOR_HUB + tristate "Surface System Aggregator Module Subsystem Device Hubs" + depends on SURFACE_AGGREGATOR + depends on SURFACE_AGGREGATOR_BUS + help + Device-hub drivers for Surface System Aggregator Module (SSAM) subsystem + devices. + + Provides subsystem hub drivers which manage client devices on various + SSAM subsystems. In some subsystems, notably the BAS subsystem managing + devices contained in the base of the Surface Book 3 and the KIP subsystem + managing type-cover devices in the Surface Pro 8 and Surface Pro X, + devices can be (hot-)removed. Hub devices and drivers are required to + manage these subdevices. + + Devices managed via these hubs are: + - Battery/AC devices (Surface Book 3). + - HID input devices (7th-generation and later models with detachable + input devices). + + Select M (recommended) or Y here if you want support for the above + mentioned devices on the corresponding Surface models. Without this + module, the respective devices mentioned above will not be instantiated + and thus any functionality provided by them will be missing, even when + drivers for these devices are present. This module only provides the + respective subsystem hubs. Both drivers and device specification (e.g. + via the Surface Aggregator Registry) for these devices still need to be + selected via other options. + config SURFACE_AGGREGATOR_REGISTRY tristate "Surface System Aggregator Module Device Registry" depends on SURFACE_AGGREGATOR depends on SURFACE_AGGREGATOR_BUS help - Device-registry and device-hubs for Surface System Aggregator Module - (SSAM) devices. + Device-registry for Surface System Aggregator Module (SSAM) devices. Provides a module and driver which act as a device-registry for SSAM client devices that cannot be detected automatically, e.g. via ACPI. - Such devices are instead provided via this registry and attached via - device hubs, also provided in this module. + Such devices are instead provided and managed via this registry. Devices provided via this registry are: - Platform profile (performance-/cooling-mode) device (5th- and later @@ -99,6 +126,36 @@ config SURFACE_AGGREGATOR_REGISTRY the respective client devices. Drivers for these devices still need to be selected via the other options. +config SURFACE_AGGREGATOR_TABLET_SWITCH + tristate "Surface Aggregator Generic Tablet-Mode Switch Driver" + depends on SURFACE_AGGREGATOR + depends on SURFACE_AGGREGATOR_BUS + depends on INPUT + help + Provides a tablet-mode switch input device on Microsoft Surface models + using the KIP subsystem for detachable keyboards (e.g. keyboard covers) + or the POS subsystem for device/screen posture changes. + + The KIP subsystem is used on newer Surface generations to handle + detachable input peripherals, specifically the keyboard cover (containing + keyboard and touchpad) on the Surface Pro 8 and Surface Pro X. The POS + subsystem is used for device posture change notifications on the Surface + Laptop Studio. This module provides a driver to let user-space know when + the device should be considered in tablet-mode due to the keyboard cover + being detached or folded back (essentially signaling when the keyboard is + not available for input). It does so by creating a tablet-mode switch + input device, sending the standard SW_TABLET_MODE event on mode change. + + Select M or Y here, if you want to provide tablet-mode switch input + events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. + +config SURFACE_BOOK1_DGPU_SWITCH + tristate "Surface Book 1 dGPU Switch Driver" + depends on SYSFS + help + This driver provides a sysfs switch to set the power-state of the + discrete GPU found on the Microsoft Surface Book 1. + config SURFACE_DTX tristate "Surface DTX (Detachment System) Driver" depends on SURFACE_AGGREGATOR diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile index 0fc9cd3e4dd97..7efcd0cdb5329 100644 --- a/drivers/platform/surface/Makefile +++ b/drivers/platform/surface/Makefile @@ -9,7 +9,10 @@ obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o +obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o +obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o +obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig index cab0203242562..c114f9dd5fe1c 100644 --- a/drivers/platform/surface/aggregator/Kconfig +++ b/drivers/platform/surface/aggregator/Kconfig @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0+ -# Copyright (C) 2019-2021 Maximilian Luz +# Copyright (C) 2019-2022 Maximilian Luz menuconfig SURFACE_AGGREGATOR tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile index c0d550eda5cd5..fdf664a217f98 100644 --- a/drivers/platform/surface/aggregator/Makefile +++ b/drivers/platform/surface/aggregator/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0+ -# Copyright (C) 2019-2021 Maximilian Luz +# Copyright (C) 2019-2022 Maximilian Luz # For include/trace/define_trace.h to include trace.h CFLAGS_core.o = -I$(src) diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c index abbbb5b08b073..96986042a2575 100644 --- a/drivers/platform/surface/aggregator/bus.c +++ b/drivers/platform/surface/aggregator/bus.c @@ -2,10 +2,11 @@ /* * Surface System Aggregator Module bus and device integration. * - * Copyright (C) 2019-2021 Maximilian Luz + * Copyright (C) 2019-2022 Maximilian Luz */ #include +#include #include #include @@ -14,6 +15,9 @@ #include "bus.h" #include "controller.h" + +/* -- Device and bus functions. --------------------------------------------- */ + static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -363,6 +367,162 @@ void ssam_device_driver_unregister(struct ssam_device_driver *sdrv) } EXPORT_SYMBOL_GPL(ssam_device_driver_unregister); + +/* -- Bus registration. ----------------------------------------------------- */ + +/** + * ssam_bus_register() - Register and set-up the SSAM client device bus. + */ +int ssam_bus_register(void) +{ + return bus_register(&ssam_bus_type); +} + +/** + * ssam_bus_unregister() - Unregister the SSAM client device bus. + */ +void ssam_bus_unregister(void) +{ + return bus_unregister(&ssam_bus_type); +} + + +/* -- Helpers for controller and hub devices. ------------------------------- */ + +static int ssam_device_uid_from_string(const char *str, struct ssam_device_uid *uid) +{ + u8 d, tc, tid, iid, fn; + int n; + + n = sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn); + if (n != 5) + return -EINVAL; + + uid->domain = d; + uid->category = tc; + uid->target = tid; + uid->instance = iid; + uid->function = fn; + + return 0; +} + +static int ssam_get_uid_for_node(struct fwnode_handle *node, struct ssam_device_uid *uid) +{ + const char* str = fwnode_get_name(node); + + /* + * To simplify definitions of firmware nodes, we set the device name + * based on the UID of the device, prefixed with "ssam:". + */ + if (strncmp(str, "ssam:", strlen("ssam:")) != 0) + return -ENODEV; + + str += strlen("ssam:"); + return ssam_device_uid_from_string(str, uid); +} + +static int ssam_add_client_device(struct device *parent, struct ssam_controller *ctrl, + struct fwnode_handle *node) +{ + struct ssam_device_uid uid; + struct ssam_device *sdev; + int status; + + status = ssam_get_uid_for_node(node, &uid); + if (status) + return status; + + sdev = ssam_device_alloc(ctrl, uid); + if (!sdev) + return -ENOMEM; + + sdev->dev.parent = parent; + sdev->dev.fwnode = node; + + status = ssam_device_add(sdev); + if (status) + ssam_device_put(sdev); + + return status; +} + +/** + * __ssam_register_clients() - Register client devices defined under the + * given firmware node as children of the given device. + * @parent: The parent device under which clients should be registered. + * @ctrl: The controller with which client should be registered. + * @node: The firmware node holding definitions of the devices to be added. + * + * Register all clients that have been defined as children of the given root + * firmware node as children of the given parent device. The respective child + * firmware nodes will be associated with the correspondingly created child + * devices. + * + * The given controller will be used to instantiate the new devices. See + * ssam_device_add() for details. + * + * Note that, generally, the use of either ssam_device_register_clients() or + * ssam_register_clients() should be preferred as they directly use the + * firmware node and/or controller associated with the given device. This + * function is only intended for use when different device specifications (e.g. + * ACPI and firmware nodes) need to be combined (as is done in the platform hub + * of the device registry). + * + * Return: Returns zero on success, nonzero on failure. + */ +int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl, + struct fwnode_handle *node) +{ + struct fwnode_handle *child; + int status; + + fwnode_for_each_child_node(node, child) { + /* + * Try to add the device specified in the firmware node. If + * this fails with -ENODEV, the node does not specify any SSAM + * device, so ignore it and continue with the next one. + */ + status = ssam_add_client_device(parent, ctrl, child); + if (status && status != -ENODEV) + goto err; + } + + return 0; +err: + ssam_remove_clients(parent); + return status; +} +EXPORT_SYMBOL_GPL(__ssam_register_clients); + +/** + * ssam_register_clients() - Register all client devices defined under the + * given parent device. + * @dev: The parent device under which clients should be registered. + * @ctrl: The controller with which client should be registered. + * + * Register all clients that have via firmware nodes been defined as children + * of the given (parent) device. The respective child firmware nodes will be + * associated with the correspondingly created child devices. + * + * The given controller will be used to instantiate the new devices. See + * ssam_device_add() for details. + * + * Return: Returns zero on success, nonzero on failure. + */ +int ssam_register_clients(struct device *dev, struct ssam_controller *ctrl) +{ + struct fwnode_handle *node; + int status; + + node = fwnode_handle_get(dev_fwnode(dev)); + status = __ssam_register_clients(dev, ctrl, node); + fwnode_handle_put(node); + + return status; +} +EXPORT_SYMBOL_GPL(ssam_register_clients); + static int ssam_remove_device(struct device *dev, void *_data) { struct ssam_device *sdev = to_ssam_device(dev); @@ -387,19 +547,3 @@ void ssam_remove_clients(struct device *dev) device_for_each_child_reverse(dev, NULL, ssam_remove_device); } EXPORT_SYMBOL_GPL(ssam_remove_clients); - -/** - * ssam_bus_register() - Register and set-up the SSAM client device bus. - */ -int ssam_bus_register(void) -{ - return bus_register(&ssam_bus_type); -} - -/** - * ssam_bus_unregister() - Unregister the SSAM client device bus. - */ -void ssam_bus_unregister(void) -{ - return bus_unregister(&ssam_bus_type); -} diff --git a/drivers/platform/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h index 6964ee84e79c7..5b4dbf21906cd 100644 --- a/drivers/platform/surface/aggregator/bus.h +++ b/drivers/platform/surface/aggregator/bus.h @@ -2,7 +2,7 @@ /* * Surface System Aggregator Module bus and device integration. * - * Copyright (C) 2019-2021 Maximilian Luz + * Copyright (C) 2019-2022 Maximilian Luz */ #ifndef _SURFACE_AGGREGATOR_BUS_H diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c index b8c377b3f9321..43e7651991371 100644 --- a/drivers/platform/surface/aggregator/controller.c +++ b/drivers/platform/surface/aggregator/controller.c @@ -2,7 +2,7 @@ /* * Main SSAM/SSH controller structure and functionality. * - * Copyright (C) 2019-2021 Maximilian Luz + * Copyright (C) 2019-2022 Maximilian Luz */ #include @@ -2199,16 +2199,26 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, } /** - * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is - * no longer in use and free the corresponding entry. + * ssam_nf_refcount_disable_free() - Disable event for reference count entry if + * it is no longer in use and free the corresponding entry. * @ctrl: The controller to disable the event on. * @entry: The reference count entry for the event to be disabled. * @flags: The flags used for enabling the event on the EC. + * @ec: Flag specifying if the event should actually be disabled on the EC. * - * If the reference count equals zero, i.e. the event is no longer requested by - * any client, the event will be disabled and the corresponding reference count - * entry freed. The reference count entry must not be used any more after a - * call to this function. + * If ``ec`` equals ``true`` and the reference count equals zero (i.e. the + * event is no longer requested by any client), the specified event will be + * disabled on the EC via the corresponding request. + * + * If ``ec`` equals ``false``, no request will be sent to the EC and the event + * can be considered in a detached state (i.e. no longer used but still + * enabled). Disabling an event via this method may be required for + * hot-removable devices, where event disable requests may time out after the + * device has been physically removed. + * + * In both cases, if the reference count equals zero, the corresponding + * reference count entry will be freed. The reference count entry must not be + * used any more after a call to this function. * * Also checks if the flags used for disabling the event match the flags used * for enabling the event and warns if they do not (regardless of reference @@ -2223,7 +2233,7 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, * returns the status of the event-enable EC command. */ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, - struct ssam_nf_refcount_entry *entry, u8 flags) + struct ssam_nf_refcount_entry *entry, u8 flags, bool ec) { const struct ssam_event_registry reg = entry->key.reg; const struct ssam_event_id id = entry->key.id; @@ -2232,8 +2242,9 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, lockdep_assert_held(&nf->lock); - ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", - reg.target_category, id.target_category, id.instance, entry->refcount); + ssam_dbg(ctrl, "%s event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", + ec ? "disabling" : "detaching", reg.target_category, id.target_category, + id.instance, entry->refcount); if (entry->flags != flags) { ssam_warn(ctrl, @@ -2242,7 +2253,7 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, id.instance); } - if (entry->refcount == 0) { + if (ec && entry->refcount == 0) { status = ssam_ssh_event_disable(ctrl, reg, id, flags); kfree(entry); } @@ -2322,20 +2333,26 @@ int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notif EXPORT_SYMBOL_GPL(ssam_notifier_register); /** - * ssam_notifier_unregister() - Unregister an event notifier. - * @ctrl: The controller the notifier has been registered on. - * @n: The event notifier to unregister. + * __ssam_notifier_unregister() - Unregister an event notifier. + * @ctrl: The controller the notifier has been registered on. + * @n: The event notifier to unregister. + * @disable: Whether to disable the corresponding event on the EC. * * Unregister an event notifier. Decrement the usage counter of the associated * SAM event if the notifier is not marked as an observer. If the usage counter - * reaches zero, the event will be disabled. + * reaches zero and ``disable`` equals ``true``, the event will be disabled. + * + * Useful for hot-removable devices, where communication may fail once the + * device has been physically removed. In that case, specifying ``disable`` as + * ``false`` avoids communication with the EC. * * Return: Returns zero on success, %-ENOENT if the given notifier block has * not been registered on the controller. If the given notifier block was the * last one associated with its specific event, returns the status of the * event-disable EC-command. */ -int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n) +int __ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n, + bool disable) { u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); struct ssam_nf_refcount_entry *entry; @@ -2373,7 +2390,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not goto remove; } - status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags); + status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags, disable); } remove: @@ -2383,7 +2400,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not return status; } -EXPORT_SYMBOL_GPL(ssam_notifier_unregister); +EXPORT_SYMBOL_GPL(__ssam_notifier_unregister); /** * ssam_controller_event_enable() - Enable the specified event. @@ -2477,7 +2494,7 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl, return -ENOENT; } - status = ssam_nf_refcount_disable_free(ctrl, entry, flags); + status = ssam_nf_refcount_disable_free(ctrl, entry, flags, true); mutex_unlock(&nf->lock); return status; diff --git a/drivers/platform/surface/aggregator/controller.h b/drivers/platform/surface/aggregator/controller.h index a0963c3562ff1..f0d987abc51e3 100644 --- a/drivers/platform/surface/aggregator/controller.h +++ b/drivers/platform/surface/aggregator/controller.h @@ -2,7 +2,7 @@ /* * Main SSAM/SSH controller structure and functionality. * - * Copyright (C) 2019-2021 Maximilian Luz + * Copyright (C) 2019-2022 Maximilian Luz */ #ifndef _SURFACE_AGGREGATOR_CONTROLLER_H diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c index a62c5dfe42d64..1a6373dea109c 100644 --- a/drivers/platform/surface/aggregator/core.c +++ b/drivers/platform/surface/aggregator/core.c @@ -7,7 +7,7 @@ * Handles communication via requests as well as enabling, disabling, and * relaying of events. * - * Copyright (C) 2019-2021 Maximilian Luz + * Copyright (C) 2019-2022 Maximilian Luz */ #include diff --git a/drivers/platform/surface/aggregator/ssh_msgb.h b/drivers/platform/surface/aggregator/ssh_msgb.h index e562958ffdf0c..f3ecad92eefd8 100644 --- a/drivers/platform/surface/aggregator/ssh_msgb.h +++ b/drivers/platform/surface/aggregator/ssh_msgb.h @@ -2,7 +2,7 @@ /* * SSH message builder functions. * - * Copyright (C) 2019-2021 Maximilian Luz + * Copyright (C) 2019-2022 Maximilian Luz */ #ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c index 8a4451c1ffe57..6748fe4ac5d5f 100644 --- a/drivers/platform/surface/aggregator/ssh_packet_layer.c +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c @@ -2,7 +2,7 @@ /* * SSH packet transport layer. * - * Copyright (C) 2019-2021 Maximilian Luz + * Copyright (C) 2019-2022 Maximilian Luz */ #include diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.h b/drivers/platform/surface/aggregator/ssh_packet_layer.h index 2eb329f0b91af..64633522f9716 100644 --- a/drivers/platform/surface/aggregator/ssh_packet_layer.h +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.h @@ -2,7 +2,7 @@ /* * SSH packet transport layer. * - * Copyright (C) 2019-2021 Maximilian Luz + * Copyright (C) 2019-2022 Maximilian Luz */ #ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H diff --git a/drivers/platform/surface/aggregator/ssh_parser.c b/drivers/platform/surface/aggregator/ssh_parser.c index b77912f8f13b2..a6f6686943652 100644 --- a/drivers/platform/surface/aggregator/ssh_parser.c +++ b/drivers/platform/surface/aggregator/ssh_parser.c @@ -2,7 +2,7 @@ /* * SSH message parser. * - * Copyright (C) 2019-2021 Maximilian Luz + * Copyright (C) 2019-2022 Maximilian Luz */ #include diff --git a/drivers/platform/surface/aggregator/ssh_parser.h b/drivers/platform/surface/aggregator/ssh_parser.h index 3bd6e180fd168..801d8fa69fb52 100644 --- a/drivers/platform/surface/aggregator/ssh_parser.h +++ b/drivers/platform/surface/aggregator/ssh_parser.h @@ -2,7 +2,7 @@ /* * SSH message parser. * - * Copyright (C) 2019-2021 Maximilian Luz + * Copyright (C) 2019-2022 Maximilian Luz */ #ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c index 790f7f0eee98b..f5565570f16c7 100644 --- a/drivers/platform/surface/aggregator/ssh_request_layer.c +++ b/drivers/platform/surface/aggregator/ssh_request_layer.c @@ -2,7 +2,7 @@ /* * SSH request transport layer. * - * Copyright (C) 2019-2021 Maximilian Luz + * Copyright (C) 2019-2022 Maximilian Luz */ #include diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.h b/drivers/platform/surface/aggregator/ssh_request_layer.h index 9c3cbae2d4bdf..4e387a0313514 100644 --- a/drivers/platform/surface/aggregator/ssh_request_layer.h +++ b/drivers/platform/surface/aggregator/ssh_request_layer.h @@ -2,7 +2,7 @@ /* * SSH request transport layer. * - * Copyright (C) 2019-2021 Maximilian Luz + * Copyright (C) 2019-2022 Maximilian Luz */ #ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h index de64cf169060c..2a2c17771d014 100644 --- a/drivers/platform/surface/aggregator/trace.h +++ b/drivers/platform/surface/aggregator/trace.h @@ -2,7 +2,7 @@ /* * Trace points for SSAM/SSH. * - * Copyright (C) 2020-2021 Maximilian Luz + * Copyright (C) 2020-2022 Maximilian Luz */ #undef TRACE_SYSTEM @@ -76,7 +76,7 @@ TRACE_DEFINE_ENUM(SSAM_SSH_TC_HID); TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCH); TRACE_DEFINE_ENUM(SSAM_SSH_TC_BKL); TRACE_DEFINE_ENUM(SSAM_SSH_TC_TAM); -TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC0); TRACE_DEFINE_ENUM(SSAM_SSH_TC_UFI); TRACE_DEFINE_ENUM(SSAM_SSH_TC_USC); TRACE_DEFINE_ENUM(SSAM_SSH_TC_PEN); @@ -85,6 +85,11 @@ TRACE_DEFINE_ENUM(SSAM_SSH_TC_AUD); TRACE_DEFINE_ENUM(SSAM_SSH_TC_SMC); TRACE_DEFINE_ENUM(SSAM_SSH_TC_KPD); TRACE_DEFINE_ENUM(SSAM_SSH_TC_REG); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_SPT); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_SYS); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC1); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_SHB); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_POS); #define SSAM_PTR_UID_LEN 9 #define SSAM_U8_FIELD_NOT_APPLICABLE ((u16)-1) @@ -229,40 +234,45 @@ static inline u32 ssam_trace_get_request_tc(const struct ssh_packet *p) #define ssam_show_ssh_tc(rqid) \ __print_symbolic(rqid, \ - { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ - { SSAM_SSH_TC_SAM, "SAM" }, \ - { SSAM_SSH_TC_BAT, "BAT" }, \ - { SSAM_SSH_TC_TMP, "TMP" }, \ - { SSAM_SSH_TC_PMC, "PMC" }, \ - { SSAM_SSH_TC_FAN, "FAN" }, \ - { SSAM_SSH_TC_PoM, "PoM" }, \ - { SSAM_SSH_TC_DBG, "DBG" }, \ - { SSAM_SSH_TC_KBD, "KBD" }, \ - { SSAM_SSH_TC_FWU, "FWU" }, \ - { SSAM_SSH_TC_UNI, "UNI" }, \ - { SSAM_SSH_TC_LPC, "LPC" }, \ - { SSAM_SSH_TC_TCL, "TCL" }, \ - { SSAM_SSH_TC_SFL, "SFL" }, \ - { SSAM_SSH_TC_KIP, "KIP" }, \ - { SSAM_SSH_TC_EXT, "EXT" }, \ - { SSAM_SSH_TC_BLD, "BLD" }, \ - { SSAM_SSH_TC_BAS, "BAS" }, \ - { SSAM_SSH_TC_SEN, "SEN" }, \ - { SSAM_SSH_TC_SRQ, "SRQ" }, \ - { SSAM_SSH_TC_MCU, "MCU" }, \ - { SSAM_SSH_TC_HID, "HID" }, \ - { SSAM_SSH_TC_TCH, "TCH" }, \ - { SSAM_SSH_TC_BKL, "BKL" }, \ - { SSAM_SSH_TC_TAM, "TAM" }, \ - { SSAM_SSH_TC_ACC, "ACC" }, \ - { SSAM_SSH_TC_UFI, "UFI" }, \ - { SSAM_SSH_TC_USC, "USC" }, \ - { SSAM_SSH_TC_PEN, "PEN" }, \ - { SSAM_SSH_TC_VID, "VID" }, \ - { SSAM_SSH_TC_AUD, "AUD" }, \ - { SSAM_SSH_TC_SMC, "SMC" }, \ - { SSAM_SSH_TC_KPD, "KPD" }, \ - { SSAM_SSH_TC_REG, "REG" } \ + { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ + { SSAM_SSH_TC_SAM, "SAM" }, \ + { SSAM_SSH_TC_BAT, "BAT" }, \ + { SSAM_SSH_TC_TMP, "TMP" }, \ + { SSAM_SSH_TC_PMC, "PMC" }, \ + { SSAM_SSH_TC_FAN, "FAN" }, \ + { SSAM_SSH_TC_PoM, "PoM" }, \ + { SSAM_SSH_TC_DBG, "DBG" }, \ + { SSAM_SSH_TC_KBD, "KBD" }, \ + { SSAM_SSH_TC_FWU, "FWU" }, \ + { SSAM_SSH_TC_UNI, "UNI" }, \ + { SSAM_SSH_TC_LPC, "LPC" }, \ + { SSAM_SSH_TC_TCL, "TCL" }, \ + { SSAM_SSH_TC_SFL, "SFL" }, \ + { SSAM_SSH_TC_KIP, "KIP" }, \ + { SSAM_SSH_TC_EXT, "EXT" }, \ + { SSAM_SSH_TC_BLD, "BLD" }, \ + { SSAM_SSH_TC_BAS, "BAS" }, \ + { SSAM_SSH_TC_SEN, "SEN" }, \ + { SSAM_SSH_TC_SRQ, "SRQ" }, \ + { SSAM_SSH_TC_MCU, "MCU" }, \ + { SSAM_SSH_TC_HID, "HID" }, \ + { SSAM_SSH_TC_TCH, "TCH" }, \ + { SSAM_SSH_TC_BKL, "BKL" }, \ + { SSAM_SSH_TC_TAM, "TAM" }, \ + { SSAM_SSH_TC_ACC0, "ACC0" }, \ + { SSAM_SSH_TC_UFI, "UFI" }, \ + { SSAM_SSH_TC_USC, "USC" }, \ + { SSAM_SSH_TC_PEN, "PEN" }, \ + { SSAM_SSH_TC_VID, "VID" }, \ + { SSAM_SSH_TC_AUD, "AUD" }, \ + { SSAM_SSH_TC_SMC, "SMC" }, \ + { SSAM_SSH_TC_KPD, "KPD" }, \ + { SSAM_SSH_TC_REG, "REG" }, \ + { SSAM_SSH_TC_SPT, "SPT" }, \ + { SSAM_SSH_TC_SYS, "SYS" }, \ + { SSAM_SSH_TC_ACC1, "ACC1" }, \ + { SSAM_SSH_TC_SHB, "SMB" }, \ + { SSAM_SSH_TC_POS, "POS" } \ ) DECLARE_EVENT_CLASS(ssam_frame_class, diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c index ca4602bcc7dea..490b9731068ae 100644 --- a/drivers/platform/surface/surface3-wmi.c +++ b/drivers/platform/surface/surface3-wmi.c @@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), }, }, + { + .matches = { + DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), + DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), + DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), + }, + }, #endif { } }; diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c index 7b758f8cc137e..b0a83255d0608 100644 --- a/drivers/platform/surface/surface_acpi_notify.c +++ b/drivers/platform/surface/surface_acpi_notify.c @@ -8,7 +8,7 @@ * notifications sent from ACPI via the SAN interface by providing them to any * registered external driver. * - * Copyright (C) 2019-2020 Maximilian Luz + * Copyright (C) 2019-2022 Maximilian Luz */ #include diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c index 30fb50fde4508..492c82e691827 100644 --- a/drivers/platform/surface/surface_aggregator_cdev.c +++ b/drivers/platform/surface/surface_aggregator_cdev.c @@ -3,7 +3,7 @@ * Provides user-space access to the SSAM EC via the /dev/surface/aggregator * misc device. Intended for debugging and development. * - * Copyright (C) 2020-2021 Maximilian Luz + * Copyright (C) 2020-2022 Maximilian Luz */ #include diff --git a/drivers/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c new file mode 100644 index 0000000000000..43061514be382 --- /dev/null +++ b/drivers/platform/surface/surface_aggregator_hub.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Surface System Aggregator Module (SSAM) subsystem device hubs. + * + * Provides a driver for SSAM subsystems device hubs. This driver performs + * instantiation of the devices managed by said hubs and takes care of + * (hot-)removal. + * + * Copyright (C) 2020-2022 Maximilian Luz + */ + +#include +#include +#include +#include +#include + +#include + + +/* -- SSAM generic subsystem hub driver framework. -------------------------- */ + +enum ssam_hub_state { + SSAM_HUB_UNINITIALIZED, /* Only set during initialization. */ + SSAM_HUB_CONNECTED, + SSAM_HUB_DISCONNECTED, +}; + +enum ssam_hub_flags { + SSAM_HUB_HOT_REMOVED, +}; + +struct ssam_hub; + +struct ssam_hub_ops { + int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); +}; + +struct ssam_hub { + struct ssam_device *sdev; + + enum ssam_hub_state state; + unsigned long flags; + + struct delayed_work update_work; + unsigned long connect_delay; + + struct ssam_event_notifier notif; + struct ssam_hub_ops ops; +}; + +struct ssam_hub_desc { + struct { + struct ssam_event_registry reg; + struct ssam_event_id id; + enum ssam_event_mask mask; + } event; + + struct { + u32 (*notify)(struct ssam_event_notifier *nf, const struct ssam_event *event); + int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); + } ops; + + unsigned long connect_delay_ms; +}; + +static void ssam_hub_update_workfn(struct work_struct *work) +{ + struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); + enum ssam_hub_state state; + int status = 0; + + status = hub->ops.get_state(hub, &state); + if (status) + return; + + /* + * There is a small possibility that hub devices were hot-removed and + * re-added before we were able to remove them here. In that case, both + * the state returned by get_state() and the state of the hub will + * equal SSAM_HUB_CONNECTED and we would bail early below, which would + * leave child devices without proper (re-)initialization and the + * hot-remove flag set. + * + * Therefore, we check whether devices have been hot-removed via an + * additional flag on the hub and, in this case, override the returned + * hub state. In case of a missed disconnect (i.e. get_state returned + * "connected"), we further need to re-schedule this work (with the + * appropriate delay) as the actual connect work submission might have + * been merged with this one. + * + * This then leads to one of two cases: Either we submit an unnecessary + * work item (which will get ignored via either the queue or the state + * checks) or, in the unlikely case that the work is actually required, + * double the normal connect delay. + */ + if (test_and_clear_bit(SSAM_HUB_HOT_REMOVED, &hub->flags)) { + if (state == SSAM_HUB_CONNECTED) + schedule_delayed_work(&hub->update_work, hub->connect_delay); + + state = SSAM_HUB_DISCONNECTED; + } + + if (hub->state == state) + return; + hub->state = state; + + if (hub->state == SSAM_HUB_CONNECTED) + status = ssam_device_register_clients(hub->sdev); + else + ssam_remove_clients(&hub->sdev->dev); + + if (status) + dev_err(&hub->sdev->dev, "failed to update hub child devices: %d\n", status); +} + +static int ssam_hub_mark_hot_removed(struct device *dev, void *_data) +{ + struct ssam_device *sdev = to_ssam_device(dev); + + if (is_ssam_device(dev)) + ssam_device_mark_hot_removed(sdev); + + return 0; +} + +static void ssam_hub_update(struct ssam_hub *hub, bool connected) +{ + unsigned long delay; + + /* Mark devices as hot-removed before we remove any. */ + if (!connected) { + set_bit(SSAM_HUB_HOT_REMOVED, &hub->flags); + device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_hub_mark_hot_removed); + } + + /* + * Delay update when the base/keyboard cover is being connected to give + * devices/EC some time to set up. + */ + delay = connected ? hub->connect_delay : 0; + + schedule_delayed_work(&hub->update_work, delay); +} + +static int __maybe_unused ssam_hub_resume(struct device *dev) +{ + struct ssam_hub *hub = dev_get_drvdata(dev); + + schedule_delayed_work(&hub->update_work, 0); + return 0; +} +static SIMPLE_DEV_PM_OPS(ssam_hub_pm_ops, NULL, ssam_hub_resume); + +static int ssam_hub_probe(struct ssam_device *sdev) +{ + const struct ssam_hub_desc *desc; + struct ssam_hub *hub; + int status; + + desc = ssam_device_get_match_data(sdev); + if (!desc) { + WARN(1, "no driver match data specified"); + return -EINVAL; + } + + hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); + if (!hub) + return -ENOMEM; + + hub->sdev = sdev; + hub->state = SSAM_HUB_UNINITIALIZED; + + hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ + hub->notif.base.fn = desc->ops.notify; + hub->notif.event.reg = desc->event.reg; + hub->notif.event.id = desc->event.id; + hub->notif.event.mask = desc->event.mask; + hub->notif.event.flags = SSAM_EVENT_SEQUENCED; + + hub->connect_delay = msecs_to_jiffies(desc->connect_delay_ms); + hub->ops.get_state = desc->ops.get_state; + + INIT_DELAYED_WORK(&hub->update_work, ssam_hub_update_workfn); + + ssam_device_set_drvdata(sdev, hub); + + status = ssam_device_notifier_register(sdev, &hub->notif); + if (status) + return status; + + schedule_delayed_work(&hub->update_work, 0); + return 0; +} + +static void ssam_hub_remove(struct ssam_device *sdev) +{ + struct ssam_hub *hub = ssam_device_get_drvdata(sdev); + + ssam_device_notifier_unregister(sdev, &hub->notif); + cancel_delayed_work_sync(&hub->update_work); + ssam_remove_clients(&sdev->dev); +} + + +/* -- SSAM base-subsystem hub driver. --------------------------------------- */ + +/* + * Some devices (especially battery) may need a bit of time to be fully usable + * after being (re-)connected. This delay has been determined via + * experimentation. + */ +#define SSAM_BASE_UPDATE_CONNECT_DELAY 2500 + +SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { + .target_category = SSAM_SSH_TC_BAS, + .target_id = 0x01, + .command_id = 0x0d, + .instance_id = 0x00, +}); + +#define SSAM_BAS_OPMODE_TABLET 0x00 +#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c + +static int ssam_base_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state) +{ + u8 opmode; + int status; + + status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode); + if (status < 0) { + dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status); + return status; + } + + if (opmode != SSAM_BAS_OPMODE_TABLET) + *state = SSAM_HUB_CONNECTED; + else + *state = SSAM_HUB_DISCONNECTED; + + return 0; +} + +static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) +{ + struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); + + if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) + return 0; + + if (event->length < 1) { + dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); + return 0; + } + + ssam_hub_update(hub, event->data[0]); + + /* + * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and + * consumed by the detachment system driver. We're just a (more or less) + * silent observer. + */ + return 0; +} + +static const struct ssam_hub_desc base_hub = { + .event = { + .reg = SSAM_EVENT_REGISTRY_SAM, + .id = { + .target_category = SSAM_SSH_TC_BAS, + .instance = 0, + }, + .mask = SSAM_EVENT_MASK_NONE, + }, + .ops = { + .notify = ssam_base_hub_notif, + .get_state = ssam_base_hub_query_state, + }, + .connect_delay_ms = SSAM_BASE_UPDATE_CONNECT_DELAY, +}; + + +/* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ + +/* + * Some devices may need a bit of time to be fully usable after being + * (re-)connected. This delay has been determined via experimentation. + */ +#define SSAM_KIP_UPDATE_CONNECT_DELAY 250 + +#define SSAM_EVENT_KIP_CID_CONNECTION 0x2c + +SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_query_state, u8, { + .target_category = SSAM_SSH_TC_KIP, + .target_id = 0x01, + .command_id = 0x2c, + .instance_id = 0x00, +}); + +static int ssam_kip_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state) +{ + int status; + u8 connected; + + status = ssam_retry(__ssam_kip_query_state, hub->sdev->ctrl, &connected); + if (status < 0) { + dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status); + return status; + } + + *state = connected ? SSAM_HUB_CONNECTED : SSAM_HUB_DISCONNECTED; + return 0; +} + +static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) +{ + struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); + + if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION) + return 0; /* Return "unhandled". */ + + if (event->length < 1) { + dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); + return 0; + } + + ssam_hub_update(hub, event->data[0]); + return SSAM_NOTIF_HANDLED; +} + +static const struct ssam_hub_desc kip_hub = { + .event = { + .reg = SSAM_EVENT_REGISTRY_SAM, + .id = { + .target_category = SSAM_SSH_TC_KIP, + .instance = 0, + }, + .mask = SSAM_EVENT_MASK_TARGET, + }, + .ops = { + .notify = ssam_kip_hub_notif, + .get_state = ssam_kip_hub_query_state, + }, + .connect_delay_ms = SSAM_KIP_UPDATE_CONNECT_DELAY, +}; + + +/* -- Driver registration. -------------------------------------------------- */ + +static const struct ssam_device_id ssam_hub_match[] = { + { SSAM_VDEV(HUB, 0x01, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub }, + { SSAM_VDEV(HUB, 0x02, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub }, + { } +}; +MODULE_DEVICE_TABLE(ssam, ssam_hub_match); + +static struct ssam_device_driver ssam_subsystem_hub_driver = { + .probe = ssam_hub_probe, + .remove = ssam_hub_remove, + .match_table = ssam_hub_match, + .driver = { + .name = "surface_aggregator_subsystem_hub", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .pm = &ssam_hub_pm_ops, + }, +}; +module_ssam_device_driver(ssam_subsystem_hub_driver); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Subsystem device hub driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index ce2bd88feeaa8..49426b6e6b19d 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -6,19 +6,16 @@ * cannot be auto-detected. Provides device-hubs and performs instantiation * for these devices. * - * Copyright (C) 2020-2021 Maximilian Luz + * Copyright (C) 2020-2022 Maximilian Luz */ #include #include -#include #include #include #include #include -#include -#include #include @@ -41,9 +38,15 @@ static const struct software_node ssam_node_root = { .name = "ssam_platform_hub", }; +/* KIP device hub (connects keyboard cover devices on Surface Pro 8). */ +static const struct software_node ssam_node_hub_kip = { + .name = "ssam:00:00:01:0e:00", + .parent = &ssam_node_root, +}; + /* Base device hub (devices attached to Surface Book 3 base). */ static const struct software_node ssam_node_hub_base = { - .name = "ssam:00:00:02:00:00", + .name = "ssam:00:00:02:11:00", .parent = &ssam_node_root, }; @@ -71,44 +74,50 @@ static const struct software_node ssam_node_tmp_pprof = { .parent = &ssam_node_root, }; +/* Tablet-mode switch via KIP subsystem. */ +static const struct software_node ssam_node_kip_tablet_switch = { + .name = "ssam:01:0e:01:00:01", + .parent = &ssam_node_root, +}; + /* DTX / detachment-system device (Surface Book 3). */ static const struct software_node ssam_node_bas_dtx = { .name = "ssam:01:11:01:00:00", .parent = &ssam_node_root, }; -/* HID keyboard (TID1). */ -static const struct software_node ssam_node_hid_tid1_keyboard = { +/* HID keyboard (SAM, TID=1). */ +static const struct software_node ssam_node_hid_sam_keyboard = { .name = "ssam:01:15:01:01:00", .parent = &ssam_node_root, }; -/* HID pen stash (TID1; pen taken / stashed away evens). */ -static const struct software_node ssam_node_hid_tid1_penstash = { +/* HID pen stash (SAM, TID=1; pen taken / stashed away evens). */ +static const struct software_node ssam_node_hid_sam_penstash = { .name = "ssam:01:15:01:02:00", .parent = &ssam_node_root, }; -/* HID touchpad (TID1). */ -static const struct software_node ssam_node_hid_tid1_touchpad = { +/* HID touchpad (SAM, TID=1). */ +static const struct software_node ssam_node_hid_sam_touchpad = { .name = "ssam:01:15:01:03:00", .parent = &ssam_node_root, }; -/* HID device instance 6 (TID1, unknown HID device). */ -static const struct software_node ssam_node_hid_tid1_iid6 = { +/* HID device instance 6 (SAM, TID=1, HID sensor collection). */ +static const struct software_node ssam_node_hid_sam_sensors = { .name = "ssam:01:15:01:06:00", .parent = &ssam_node_root, }; -/* HID device instance 7 (TID1, unknown HID device). */ -static const struct software_node ssam_node_hid_tid1_iid7 = { +/* HID device instance 7 (SAM, TID=1, UCM USCI HID client). */ +static const struct software_node ssam_node_hid_sam_ucm_usci = { .name = "ssam:01:15:01:07:00", .parent = &ssam_node_root, }; -/* HID system controls (TID1). */ -static const struct software_node ssam_node_hid_tid1_sysctrl = { +/* HID system controls (SAM, TID=1). */ +static const struct software_node ssam_node_hid_sam_sysctrl = { .name = "ssam:01:15:01:08:00", .parent = &ssam_node_root, }; @@ -155,6 +164,36 @@ static const struct software_node ssam_node_hid_base_iid6 = { .parent = &ssam_node_hub_base, }; +/* HID keyboard (KIP hub). */ +static const struct software_node ssam_node_hid_kip_keyboard = { + .name = "ssam:01:15:02:01:00", + .parent = &ssam_node_hub_kip, +}; + +/* HID pen stash (KIP hub; pen taken / stashed away evens). */ +static const struct software_node ssam_node_hid_kip_penstash = { + .name = "ssam:01:15:02:02:00", + .parent = &ssam_node_hub_kip, +}; + +/* HID touchpad (KIP hub). */ +static const struct software_node ssam_node_hid_kip_touchpad = { + .name = "ssam:01:15:02:03:00", + .parent = &ssam_node_hub_kip, +}; + +/* HID device instance 5 (KIP hub, type-cover firmware update). */ +static const struct software_node ssam_node_hid_kip_fwupd = { + .name = "ssam:01:15:02:05:00", + .parent = &ssam_node_hub_kip, +}; + +/* Tablet-mode switch via POS subsystem. */ +static const struct software_node ssam_node_pos_tablet_switch = { + .name = "ssam:01:26:01:00:01", + .parent = &ssam_node_root, +}; + /* * Devices for 5th- and 6th-generations models: * - Surface Book 2, @@ -201,12 +240,13 @@ static const struct software_node *ssam_node_group_sls[] = { &ssam_node_bat_ac, &ssam_node_bat_main, &ssam_node_tmp_pprof, - &ssam_node_hid_tid1_keyboard, - &ssam_node_hid_tid1_penstash, - &ssam_node_hid_tid1_touchpad, - &ssam_node_hid_tid1_iid6, - &ssam_node_hid_tid1_iid7, - &ssam_node_hid_tid1_sysctrl, + &ssam_node_pos_tablet_switch, + &ssam_node_hid_sam_keyboard, + &ssam_node_hid_sam_penstash, + &ssam_node_hid_sam_touchpad, + &ssam_node_hid_sam_sensors, + &ssam_node_hid_sam_ucm_usci, + &ssam_node_hid_sam_sysctrl, NULL, }; @@ -230,290 +270,21 @@ static const struct software_node *ssam_node_group_sp7[] = { static const struct software_node *ssam_node_group_sp8[] = { &ssam_node_root, + &ssam_node_hub_kip, &ssam_node_bat_ac, &ssam_node_bat_main, &ssam_node_tmp_pprof, - /* TODO: Add support for keyboard cover. */ + &ssam_node_kip_tablet_switch, + &ssam_node_hid_kip_keyboard, + &ssam_node_hid_kip_penstash, + &ssam_node_hid_kip_touchpad, + &ssam_node_hid_kip_fwupd, + &ssam_node_hid_sam_sensors, + &ssam_node_hid_sam_ucm_usci, NULL, }; -/* -- Device registry helper functions. ------------------------------------- */ - -static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid) -{ - u8 d, tc, tid, iid, fn; - int n; - - n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn); - if (n != 5) - return -EINVAL; - - uid->domain = d; - uid->category = tc; - uid->target = tid; - uid->instance = iid; - uid->function = fn; - - return 0; -} - -static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl, - struct fwnode_handle *node) -{ - struct ssam_device_uid uid; - struct ssam_device *sdev; - int status; - - status = ssam_uid_from_string(fwnode_get_name(node), &uid); - if (status) - return status; - - sdev = ssam_device_alloc(ctrl, uid); - if (!sdev) - return -ENOMEM; - - sdev->dev.parent = parent; - sdev->dev.fwnode = node; - - status = ssam_device_add(sdev); - if (status) - ssam_device_put(sdev); - - return status; -} - -static int ssam_hub_register_clients(struct device *parent, struct ssam_controller *ctrl, - struct fwnode_handle *node) -{ - struct fwnode_handle *child; - int status; - - fwnode_for_each_child_node(node, child) { - /* - * Try to add the device specified in the firmware node. If - * this fails with -EINVAL, the node does not specify any SSAM - * device, so ignore it and continue with the next one. - */ - - status = ssam_hub_add_device(parent, ctrl, child); - if (status && status != -EINVAL) - goto err; - } - - return 0; -err: - ssam_remove_clients(parent); - return status; -} - - -/* -- SSAM base-hub driver. ------------------------------------------------- */ - -/* - * Some devices (especially battery) may need a bit of time to be fully usable - * after being (re-)connected. This delay has been determined via - * experimentation. - */ -#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500) - -enum ssam_base_hub_state { - SSAM_BASE_HUB_UNINITIALIZED, - SSAM_BASE_HUB_CONNECTED, - SSAM_BASE_HUB_DISCONNECTED, -}; - -struct ssam_base_hub { - struct ssam_device *sdev; - - enum ssam_base_hub_state state; - struct delayed_work update_work; - - struct ssam_event_notifier notif; -}; - -SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { - .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, - .command_id = 0x0d, - .instance_id = 0x00, -}); - -#define SSAM_BAS_OPMODE_TABLET 0x00 -#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c - -static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_hub_state *state) -{ - u8 opmode; - int status; - - status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode); - if (status < 0) { - dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status); - return status; - } - - if (opmode != SSAM_BAS_OPMODE_TABLET) - *state = SSAM_BASE_HUB_CONNECTED; - else - *state = SSAM_BASE_HUB_DISCONNECTED; - - return 0; -} - -static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct ssam_base_hub *hub = dev_get_drvdata(dev); - bool connected = hub->state == SSAM_BASE_HUB_CONNECTED; - - return sysfs_emit(buf, "%d\n", connected); -} - -static struct device_attribute ssam_base_hub_attr_state = - __ATTR(state, 0444, ssam_base_hub_state_show, NULL); - -static struct attribute *ssam_base_hub_attrs[] = { - &ssam_base_hub_attr_state.attr, - NULL, -}; - -static const struct attribute_group ssam_base_hub_group = { - .attrs = ssam_base_hub_attrs, -}; - -static void ssam_base_hub_update_workfn(struct work_struct *work) -{ - struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work); - struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); - enum ssam_base_hub_state state; - int status = 0; - - status = ssam_base_hub_query_state(hub, &state); - if (status) - return; - - if (hub->state == state) - return; - hub->state = state; - - if (hub->state == SSAM_BASE_HUB_CONNECTED) - status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); - else - ssam_remove_clients(&hub->sdev->dev); - - if (status) - dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); -} - -static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -{ - struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif); - unsigned long delay; - - if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) - return 0; - - if (event->length < 1) { - dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); - return 0; - } - - /* - * Delay update when the base is being connected to give devices/EC - * some time to set up. - */ - delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0; - - schedule_delayed_work(&hub->update_work, delay); - - /* - * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and - * consumed by the detachment system driver. We're just a (more or less) - * silent observer. - */ - return 0; -} - -static int __maybe_unused ssam_base_hub_resume(struct device *dev) -{ - struct ssam_base_hub *hub = dev_get_drvdata(dev); - - schedule_delayed_work(&hub->update_work, 0); - return 0; -} -static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume); - -static int ssam_base_hub_probe(struct ssam_device *sdev) -{ - struct ssam_base_hub *hub; - int status; - - hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); - if (!hub) - return -ENOMEM; - - hub->sdev = sdev; - hub->state = SSAM_BASE_HUB_UNINITIALIZED; - - hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ - hub->notif.base.fn = ssam_base_hub_notif; - hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; - hub->notif.event.id.target_category = SSAM_SSH_TC_BAS, - hub->notif.event.id.instance = 0, - hub->notif.event.mask = SSAM_EVENT_MASK_NONE; - hub->notif.event.flags = SSAM_EVENT_SEQUENCED; - - INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn); - - ssam_device_set_drvdata(sdev, hub); - - status = ssam_notifier_register(sdev->ctrl, &hub->notif); - if (status) - return status; - - status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group); - if (status) - goto err; - - schedule_delayed_work(&hub->update_work, 0); - return 0; - -err: - ssam_notifier_unregister(sdev->ctrl, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); - ssam_remove_clients(&sdev->dev); - return status; -} - -static void ssam_base_hub_remove(struct ssam_device *sdev) -{ - struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev); - - sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); - - ssam_notifier_unregister(sdev->ctrl, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); - ssam_remove_clients(&sdev->dev); -} - -static const struct ssam_device_id ssam_base_hub_match[] = { - { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) }, - { }, -}; - -static struct ssam_device_driver ssam_base_hub_driver = { - .probe = ssam_base_hub_probe, - .remove = ssam_base_hub_remove, - .match_table = ssam_base_hub_match, - .driver = { - .name = "surface_aggregator_base_hub", - .probe_type = PROBE_PREFER_ASYNCHRONOUS, - .pm = &ssam_base_hub_pm_ops, - }, -}; - - /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ static const struct acpi_device_id ssam_platform_hub_match[] = { @@ -556,6 +327,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { /* Surface Laptop Go 1 */ { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, + /* Surface Laptop Go 2 */ + { "MSHW0290", (unsigned long)ssam_node_group_slg1 }, + /* Surface Laptop Studio */ { "MSHW0123", (unsigned long)ssam_node_group_sls }, @@ -597,7 +371,7 @@ static int ssam_platform_hub_probe(struct platform_device *pdev) set_secondary_fwnode(&pdev->dev, root); - status = ssam_hub_register_clients(&pdev->dev, ctrl, root); + status = __ssam_register_clients(&pdev->dev, ctrl, root); if (status) { set_secondary_fwnode(&pdev->dev, NULL); software_node_unregister_node_group(nodes); @@ -626,32 +400,7 @@ static struct platform_driver ssam_platform_hub_driver = { .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, }; - - -/* -- Module initialization. ------------------------------------------------ */ - -static int __init ssam_device_hub_init(void) -{ - int status; - - status = platform_driver_register(&ssam_platform_hub_driver); - if (status) - return status; - - status = ssam_device_driver_register(&ssam_base_hub_driver); - if (status) - platform_driver_unregister(&ssam_platform_hub_driver); - - return status; -} -module_init(ssam_device_hub_init); - -static void __exit ssam_device_hub_exit(void) -{ - ssam_device_driver_unregister(&ssam_base_hub_driver); - platform_driver_unregister(&ssam_platform_hub_driver); -} -module_exit(ssam_device_hub_exit); +module_platform_driver(ssam_platform_hub_driver); MODULE_AUTHOR("Maximilian Luz "); MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module"); diff --git a/drivers/platform/surface/surface_aggregator_tabletsw.c b/drivers/platform/surface/surface_aggregator_tabletsw.c new file mode 100644 index 0000000000000..6f402d2ca8945 --- /dev/null +++ b/drivers/platform/surface/surface_aggregator_tabletsw.c @@ -0,0 +1,535 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface System Aggregator Module (SSAM) tablet mode switch driver. + * + * Copyright (C) 2022 Maximilian Luz + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + + +/* -- SSAM generic tablet switch driver framework. -------------------------- */ + +struct ssam_tablet_sw; + +struct ssam_tablet_sw_ops { + int (*get_state)(struct ssam_tablet_sw *sw, u32 *state); + const char *(*state_name)(struct ssam_tablet_sw *sw, u32 state); + bool (*state_is_tablet_mode)(struct ssam_tablet_sw *sw, u32 state); +}; + +struct ssam_tablet_sw { + struct ssam_device *sdev; + + u32 state; + struct work_struct update_work; + struct input_dev *mode_switch; + + struct ssam_tablet_sw_ops ops; + struct ssam_event_notifier notif; +}; + +struct ssam_tablet_sw_desc { + struct { + const char *name; + const char *phys; + } dev; + + struct { + u32 (*notify)(struct ssam_event_notifier *nf, const struct ssam_event *event); + int (*get_state)(struct ssam_tablet_sw *sw, u32 *state); + const char *(*state_name)(struct ssam_tablet_sw *sw, u32 state); + bool (*state_is_tablet_mode)(struct ssam_tablet_sw *sw, u32 state); + } ops; + + struct { + struct ssam_event_registry reg; + struct ssam_event_id id; + enum ssam_event_mask mask; + u8 flags; + } event; +}; + +static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ssam_tablet_sw *sw = dev_get_drvdata(dev); + const char *state = sw->ops.state_name(sw, sw->state); + + return sysfs_emit(buf, "%s\n", state); +} +static DEVICE_ATTR_RO(state); + +static struct attribute *ssam_tablet_sw_attrs[] = { + &dev_attr_state.attr, + NULL, +}; + +static const struct attribute_group ssam_tablet_sw_group = { + .attrs = ssam_tablet_sw_attrs, +}; + +static void ssam_tablet_sw_update_workfn(struct work_struct *work) +{ + struct ssam_tablet_sw *sw = container_of(work, struct ssam_tablet_sw, update_work); + int tablet, status; + u32 state; + + status = sw->ops.get_state(sw, &state); + if (status) + return; + + if (sw->state == state) + return; + sw->state = state; + + /* Send SW_TABLET_MODE event. */ + tablet = sw->ops.state_is_tablet_mode(sw, state); + input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); + input_sync(sw->mode_switch); +} + +static int __maybe_unused ssam_tablet_sw_resume(struct device *dev) +{ + struct ssam_tablet_sw *sw = dev_get_drvdata(dev); + + schedule_work(&sw->update_work); + return 0; +} +static SIMPLE_DEV_PM_OPS(ssam_tablet_sw_pm_ops, NULL, ssam_tablet_sw_resume); + +static int ssam_tablet_sw_probe(struct ssam_device *sdev) +{ + const struct ssam_tablet_sw_desc *desc; + struct ssam_tablet_sw *sw; + int tablet, status; + + desc = ssam_device_get_match_data(sdev); + if (!desc) { + WARN(1, "no driver match data specified"); + return -EINVAL; + } + + sw = devm_kzalloc(&sdev->dev, sizeof(*sw), GFP_KERNEL); + if (!sw) + return -ENOMEM; + + sw->sdev = sdev; + + sw->ops.get_state = desc->ops.get_state; + sw->ops.state_name = desc->ops.state_name; + sw->ops.state_is_tablet_mode = desc->ops.state_is_tablet_mode; + + INIT_WORK(&sw->update_work, ssam_tablet_sw_update_workfn); + + ssam_device_set_drvdata(sdev, sw); + + /* Get initial state. */ + status = sw->ops.get_state(sw, &sw->state); + if (status) + return status; + + /* Set up tablet mode switch. */ + sw->mode_switch = devm_input_allocate_device(&sdev->dev); + if (!sw->mode_switch) + return -ENOMEM; + + sw->mode_switch->name = desc->dev.name; + sw->mode_switch->phys = desc->dev.phys; + sw->mode_switch->id.bustype = BUS_HOST; + sw->mode_switch->dev.parent = &sdev->dev; + + tablet = sw->ops.state_is_tablet_mode(sw, sw->state); + input_set_capability(sw->mode_switch, EV_SW, SW_TABLET_MODE); + input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); + + status = input_register_device(sw->mode_switch); + if (status) + return status; + + /* Set up notifier. */ + sw->notif.base.priority = 0; + sw->notif.base.fn = desc->ops.notify; + sw->notif.event.reg = desc->event.reg; + sw->notif.event.id = desc->event.id; + sw->notif.event.mask = desc->event.mask; + sw->notif.event.flags = SSAM_EVENT_SEQUENCED; + + status = ssam_device_notifier_register(sdev, &sw->notif); + if (status) + return status; + + status = sysfs_create_group(&sdev->dev.kobj, &ssam_tablet_sw_group); + if (status) + goto err; + + /* We might have missed events during setup, so check again. */ + schedule_work(&sw->update_work); + return 0; + +err: + ssam_device_notifier_unregister(sdev, &sw->notif); + cancel_work_sync(&sw->update_work); + return status; +} + +static void ssam_tablet_sw_remove(struct ssam_device *sdev) +{ + struct ssam_tablet_sw *sw = ssam_device_get_drvdata(sdev); + + sysfs_remove_group(&sdev->dev.kobj, &ssam_tablet_sw_group); + + ssam_device_notifier_unregister(sdev, &sw->notif); + cancel_work_sync(&sw->update_work); +} + + +/* -- SSAM KIP tablet switch implementation. -------------------------------- */ + +#define SSAM_EVENT_KIP_CID_COVER_STATE_CHANGED 0x1d + +enum ssam_kip_cover_state { + SSAM_KIP_COVER_STATE_DISCONNECTED = 0x01, + SSAM_KIP_COVER_STATE_CLOSED = 0x02, + SSAM_KIP_COVER_STATE_LAPTOP = 0x03, + SSAM_KIP_COVER_STATE_FOLDED_CANVAS = 0x04, + SSAM_KIP_COVER_STATE_FOLDED_BACK = 0x05, +}; + +static const char* ssam_kip_cover_state_name(struct ssam_tablet_sw *sw, u32 state) +{ + switch (state) { + case SSAM_KIP_COVER_STATE_DISCONNECTED: + return "disconnected"; + + case SSAM_KIP_COVER_STATE_CLOSED: + return "closed"; + + case SSAM_KIP_COVER_STATE_LAPTOP: + return "laptop"; + + case SSAM_KIP_COVER_STATE_FOLDED_CANVAS: + return "folded-canvas"; + + case SSAM_KIP_COVER_STATE_FOLDED_BACK: + return "folded-back"; + + default: + dev_warn(&sw->sdev->dev, "unknown KIP cover state: %u\n", state); + return ""; + } +} + +static bool ssam_kip_cover_state_is_tablet_mode(struct ssam_tablet_sw *sw, u32 state) +{ + switch (state) { + case SSAM_KIP_COVER_STATE_DISCONNECTED: + case SSAM_KIP_COVER_STATE_FOLDED_CANVAS: + case SSAM_KIP_COVER_STATE_FOLDED_BACK: + return true; + + case SSAM_KIP_COVER_STATE_CLOSED: + case SSAM_KIP_COVER_STATE_LAPTOP: + return false; + + default: + dev_warn(&sw->sdev->dev, "unknown KIP cover state: %d\n", sw->state); + return true; + } +} + +SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_cover_state, u8, { + .target_category = SSAM_SSH_TC_KIP, + .target_id = 0x01, + .command_id = 0x1d, + .instance_id = 0x00, +}); + +static int ssam_kip_get_cover_state(struct ssam_tablet_sw *sw, u32 *state) +{ + int status; + u8 raw; + + status = ssam_retry(__ssam_kip_get_cover_state, sw->sdev->ctrl, &raw); + if (status < 0) { + dev_err(&sw->sdev->dev, "failed to query KIP lid state: %d\n", status); + return status; + } + + *state = raw; + return 0; +} + +static u32 ssam_kip_sw_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) +{ + struct ssam_tablet_sw *sw = container_of(nf, struct ssam_tablet_sw, notif); + + if (event->command_id != SSAM_EVENT_KIP_CID_COVER_STATE_CHANGED) + return 0; /* Return "unhandled". */ + + if (event->length < 1) { + dev_warn(&sw->sdev->dev, "unexpected payload size: %u\n", event->length); + } + + schedule_work(&sw->update_work); + return SSAM_NOTIF_HANDLED; +} + +static const struct ssam_tablet_sw_desc ssam_kip_sw_desc = { + .dev = { + .name = "Microsoft Surface KIP Tablet Mode Switch", + .phys = "ssam/01:0e:01:00:01/input0", + }, + .ops = { + .notify = ssam_kip_sw_notif, + .get_state = ssam_kip_get_cover_state, + .state_name = ssam_kip_cover_state_name, + .state_is_tablet_mode = ssam_kip_cover_state_is_tablet_mode, + }, + .event = { + .reg = SSAM_EVENT_REGISTRY_SAM, + .id = { + .target_category = SSAM_SSH_TC_KIP, + .instance = 0, + }, + .mask = SSAM_EVENT_MASK_TARGET, + }, +}; + + +/* -- SSAM POS tablet switch implementation. -------------------------------- */ + +static bool tablet_mode_in_slate_state = true; +module_param(tablet_mode_in_slate_state, bool, S_IRUGO); +MODULE_PARM_DESC(tablet_mode_in_slate_state, "Enable tablet mode in slate device posture, default is 'true'"); + +#define SSAM_EVENT_POS_CID_POSTURE_CHANGED 0x03 +#define SSAM_POS_MAX_SOURCES 4 + +enum ssam_pos_state { + SSAM_POS_POSTURE_LID_CLOSED = 0x00, + SSAM_POS_POSTURE_LAPTOP = 0x01, + SSAM_POS_POSTURE_SLATE = 0x02, + SSAM_POS_POSTURE_TABLET = 0x03, +}; + +struct ssam_sources_list { + __le32 count; + __le32 id[SSAM_POS_MAX_SOURCES]; +} __packed; + +static const char* ssam_pos_state_name(struct ssam_tablet_sw *sw, u32 state) +{ + switch (state) { + case SSAM_POS_POSTURE_LID_CLOSED: + return "closed"; + + case SSAM_POS_POSTURE_LAPTOP: + return "laptop"; + + case SSAM_POS_POSTURE_SLATE: + return "slate"; + + case SSAM_POS_POSTURE_TABLET: + return "tablet"; + + default: + dev_warn(&sw->sdev->dev, "unknown device posture: %u\n", state); + return ""; + } +} + +static bool ssam_pos_state_is_tablet_mode(struct ssam_tablet_sw *sw, u32 state) +{ + switch (state) { + case SSAM_POS_POSTURE_LAPTOP: + case SSAM_POS_POSTURE_LID_CLOSED: + return false; + + case SSAM_POS_POSTURE_SLATE: + return tablet_mode_in_slate_state; + + case SSAM_POS_POSTURE_TABLET: + return true; + + default: + dev_warn(&sw->sdev->dev, "unknown device posture: %u\n", state); + return true; + } +} + +static int ssam_pos_get_sources_list(struct ssam_tablet_sw *sw, struct ssam_sources_list *sources) +{ + struct ssam_request rqst; + struct ssam_response rsp; + int status; + + rqst.target_category = SSAM_SSH_TC_POS; + rqst.target_id = 0x01; + rqst.command_id = 0x01; + rqst.instance_id = 0x00; + rqst.flags = SSAM_REQUEST_HAS_RESPONSE; + rqst.length = 0; + rqst.payload = NULL; + + rsp.capacity = sizeof(*sources); + rsp.length = 0; + rsp.pointer = (u8 *)sources; + + status = ssam_retry(ssam_request_sync_onstack, sw->sdev->ctrl, &rqst, &rsp, 0); + if (status) + return status; + + /* We need at least the 'sources->count' field. */ + if (rsp.length < sizeof(__le32)) { + dev_err(&sw->sdev->dev, "received source list response is too small\n"); + return -EPROTO; + } + + /* Make sure 'sources->count' matches with the response length. */ + if (get_unaligned_le32(&sources->count) * sizeof(__le32) + sizeof(__le32) != rsp.length) { + dev_err(&sw->sdev->dev, "mismatch between number of sources and response size\n"); + return -EPROTO; + } + + return 0; +} + +static int ssam_pos_get_source(struct ssam_tablet_sw *sw, u32 *source_id) +{ + struct ssam_sources_list sources = {}; + int status; + + status = ssam_pos_get_sources_list(sw, &sources); + if (status) + return status; + + if (sources.count == 0) { + dev_err(&sw->sdev->dev, "no posture sources found\n"); + return -ENODEV; + } + + /* + * We currently don't know what to do with more than one posture souce. + * At the moment, only one source seems to be used/provided. The + * WARN_ON() here should hopefully let us know quickly once there is a + * device that provides multiple sources, at which point we can then + * try to figure out how to handle them. + */ + WARN_ON(sources.count > 1); + + *source_id = get_unaligned_le32(&sources.id[0]); + return 0; +} + +SSAM_DEFINE_SYNC_REQUEST_WR(__ssam_pos_get_posture_for_source, __le32, __le32, { + .target_category = SSAM_SSH_TC_POS, + .target_id = 0x01, + .command_id = 0x02, + .instance_id = 0x00, +}); + +static int ssam_pos_get_posture_for_source(struct ssam_tablet_sw *sw, u32 source_id, u32 *posture) +{ + __le32 source_le = cpu_to_le32(source_id); + __le32 rspval_le = 0; + int status; + + status = ssam_retry(__ssam_pos_get_posture_for_source, sw->sdev->ctrl, + &source_le, &rspval_le); + if (status) + return status; + + *posture = le32_to_cpu(rspval_le); + return 0; +} + +static int ssam_pos_get_posture(struct ssam_tablet_sw *sw, u32 *state) +{ + u32 source_id; + int status; + + status = ssam_pos_get_source(sw, &source_id); + if (status) { + dev_err(&sw->sdev->dev, "failed to get posture source ID: %d\n", status); + return status; + } + + status = ssam_pos_get_posture_for_source(sw, source_id, state); + if (status) { + dev_err(&sw->sdev->dev, "failed to get posture value for source %u: %d\n", + source_id, status); + return status; + } + + return 0; +} + +static u32 ssam_pos_sw_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) +{ + struct ssam_tablet_sw *sw = container_of(nf, struct ssam_tablet_sw, notif); + + if (event->command_id != SSAM_EVENT_POS_CID_POSTURE_CHANGED) + return 0; /* Return "unhandled". */ + + if (event->length != sizeof(__le32) * 3) { + dev_warn(&sw->sdev->dev, "unexpected payload size: %u\n", event->length); + } + + schedule_work(&sw->update_work); + return SSAM_NOTIF_HANDLED; +} + +static const struct ssam_tablet_sw_desc ssam_pos_sw_desc = { + .dev = { + .name = "Microsoft Surface POS Tablet Mode Switch", + .phys = "ssam/01:26:01:00:01/input0", + }, + .ops = { + .notify = ssam_pos_sw_notif, + .get_state = ssam_pos_get_posture, + .state_name = ssam_pos_state_name, + .state_is_tablet_mode = ssam_pos_state_is_tablet_mode, + }, + .event = { + .reg = SSAM_EVENT_REGISTRY_SAM, + .id = { + .target_category = SSAM_SSH_TC_POS, + .instance = 0, + }, + .mask = SSAM_EVENT_MASK_TARGET, + }, +}; + + +/* -- Driver registration. -------------------------------------------------- */ + +static const struct ssam_device_id ssam_tablet_sw_match[] = { + { SSAM_SDEV(KIP, 0x01, 0x00, 0x01), (unsigned long)&ssam_kip_sw_desc }, + { SSAM_SDEV(POS, 0x01, 0x00, 0x01), (unsigned long)&ssam_pos_sw_desc }, + { }, +}; +MODULE_DEVICE_TABLE(ssam, ssam_tablet_sw_match); + +static struct ssam_device_driver ssam_tablet_sw_driver = { + .probe = ssam_tablet_sw_probe, + .remove = ssam_tablet_sw_remove, + .match_table = ssam_tablet_sw_match, + .driver = { + .name = "surface_aggregator_tablet_mode_switch", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .pm = &ssam_tablet_sw_pm_ops, + }, +}; +module_ssam_device_driver(ssam_tablet_sw_driver); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Tablet mode switch driver for Surface devices using the Surface Aggregator Module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c index 1203b9a829939..ed36944467f9f 100644 --- a/drivers/platform/surface/surface_dtx.c +++ b/drivers/platform/surface/surface_dtx.c @@ -8,7 +8,7 @@ * acknowledge (to speed things up), abort (e.g. in case the dGPU is still in * use), or request detachment via user-space. * - * Copyright (C) 2019-2021 Maximilian Luz + * Copyright (C) 2019-2022 Maximilian Luz */ #include diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c index ec66fde28e75a..c219b840d491a 100644 --- a/drivers/platform/surface/surface_gpe.c +++ b/drivers/platform/surface/surface_gpe.c @@ -4,7 +4,7 @@ * properly configuring the respective GPEs. Required for wakeup via lid on * newer Intel-based Microsoft Surface devices. * - * Copyright (C) 2020 Maximilian Luz + * Copyright (C) 2020-2022 Maximilian Luz */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -171,6 +171,18 @@ static const struct dmi_system_id dmi_lid_device_table[] = { }, .driver_data = (void *)lid_device_props_l4D, }, + { + .ident = "Surface Laptop 4 (Intel 13\")", + .matches = { + /* + * We match for SKU here due to different variants: The + * AMD (15") version does not rely on GPEs. + */ + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1950:1951"), + }, + .driver_data = (void *)lid_device_props_l4B, + }, { .ident = "Surface Laptop Studio", .matches = { diff --git a/drivers/platform/surface/surface_hotplug.c b/drivers/platform/surface/surface_hotplug.c index cfcc15cfbacb3..f004a24952013 100644 --- a/drivers/platform/surface/surface_hotplug.c +++ b/drivers/platform/surface/surface_hotplug.c @@ -10,7 +10,7 @@ * Event signaling is handled via ACPI, which will generate the appropriate * device-check notifications to be picked up by the PCIe hot-plug driver. * - * Copyright (C) 2019-2021 Maximilian Luz + * Copyright (C) 2019-2022 Maximilian Luz */ #include diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c index 6373d3b5eb7f8..fbf2e11fd6ce7 100644 --- a/drivers/platform/surface/surface_platform_profile.c +++ b/drivers/platform/surface/surface_platform_profile.c @@ -3,7 +3,7 @@ * Surface Platform Profile / Performance Mode driver for Surface System * Aggregator Module (thermal subsystem). * - * Copyright (C) 2021 Maximilian Luz + * Copyright (C) 2021-2022 Maximilian Luz */ #include diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c new file mode 100644 index 0000000000000..8b816ed8f35c6 --- /dev/null +++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include + + +#ifdef pr_fmt +#undef pr_fmt +#endif +#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ + + +static const guid_t dgpu_sw_guid = GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, + 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); + +#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" +#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" +#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" + + +static int sb1_dgpu_sw_dsmcall(void) +{ + union acpi_object *ret; + acpi_handle handle; + acpi_status status; + + status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); + if (status) + return -EINVAL; + + ret = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); + if (!ret) + return -EINVAL; + + ACPI_FREE(ret); + return 0; +} + +static int sb1_dgpu_sw_hgon(void) +{ + struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; + acpi_status status; + + status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); + if (status) { + pr_err("failed to run HGON: %d\n", status); + return -EINVAL; + } + + if (buf.pointer) + ACPI_FREE(buf.pointer); + + pr_info("turned-on dGPU via HGON\n"); + return 0; +} + +static int sb1_dgpu_sw_hgof(void) +{ + struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; + acpi_status status; + + status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); + if (status) { + pr_err("failed to run HGOF: %d\n", status); + return -EINVAL; + } + + if (buf.pointer) + ACPI_FREE(buf.pointer); + + pr_info("turned-off dGPU via HGOF\n"); + return 0; +} + + +static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + int status, value; + + status = kstrtoint(buf, 0, &value); + if (status < 0) + return status; + + if (value != 1) + return -EINVAL; + + status = sb1_dgpu_sw_dsmcall(); + + return status < 0 ? status : len; +} + +static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + bool power; + int status; + + status = kstrtobool(buf, &power); + if (status < 0) + return status; + + if (power) + status = sb1_dgpu_sw_hgon(); + else + status = sb1_dgpu_sw_hgof(); + + return status < 0 ? status : len; +} + +static DEVICE_ATTR_WO(dgpu_dsmcall); +static DEVICE_ATTR_WO(dgpu_power); + +static struct attribute *sb1_dgpu_sw_attrs[] = { + &dev_attr_dgpu_dsmcall.attr, + &dev_attr_dgpu_power.attr, + NULL, +}; + +static const struct attribute_group sb1_dgpu_sw_attr_group = { + .attrs = sb1_dgpu_sw_attrs, +}; + + +static int sb1_dgpu_sw_probe(struct platform_device *pdev) +{ + return sysfs_create_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); +} + +static int sb1_dgpu_sw_remove(struct platform_device *pdev) +{ + sysfs_remove_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); + return 0; +} + +/* + * The dGPU power seems to be actually handled by MSHW0040. However, that is + * also the power-/volume-button device with a mainline driver. So let's use + * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. + */ +static const struct acpi_device_id sb1_dgpu_sw_match[] = { + { "MSHW0041", }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); + +static struct platform_driver sb1_dgpu_sw = { + .probe = sb1_dgpu_sw_probe, + .remove = sb1_dgpu_sw_remove, + .driver = { + .name = "surfacebook1_dgpu_switch", + .acpi_match_table = sb1_dgpu_sw_match, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_platform_driver(sb1_dgpu_sw); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c index 242fb690dcaf7..30eea54dbb477 100644 --- a/drivers/platform/surface/surfacepro3_button.c +++ b/drivers/platform/surface/surfacepro3_button.c @@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) /* * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device * ID (MSHW0040) for the power/volume buttons. Make sure this is the right - * device by checking for the _DSM method and OEM Platform Revision. + * device by checking for the _DSM method and OEM Platform Revision DSM + * function. * * Returns true if the driver should bind to this device, i.e. the device is * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. @@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) static bool surface_button_check_MSHW0040(struct acpi_device *dev) { acpi_handle handle = dev->handle; - union acpi_object *result; - u64 oem_platform_rev = 0; // valid revisions are nonzero - - // get OEM platform revision - result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, - MSHW0040_DSM_REVISION, - MSHW0040_DSM_GET_OMPR, - NULL, ACPI_TYPE_INTEGER); - - /* - * If evaluating the _DSM fails, the method is not present. This means - * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we - * should use this driver. We use revision 0 indicating it is - * unavailable. - */ - - if (result) { - oem_platform_rev = result->integer.value; - ACPI_FREE(result); - } - - dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); - return oem_platform_rev == 0; + // make sure that OEM platform revision DSM call does not exist + return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, + MSHW0040_DSM_REVISION, + BIT(MSHW0040_DSM_GET_OMPR)); } diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c index 22f61b47f9e54..e1de1ff40bbae 100644 --- a/drivers/platform/x86/intel/int3472/tps68470.c +++ b/drivers/platform/x86/intel/int3472/tps68470.c @@ -45,6 +45,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) return ret; } + /* Enable I2C daisy chain */ + ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); + if (ret) { + dev_err(dev, "Failed to enable i2c daisy chain\n"); + return ret; + } + dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); return 0; diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c index 5ec2e6bb2465c..540707882bb0a 100644 --- a/drivers/power/supply/surface_battery.c +++ b/drivers/power/supply/surface_battery.c @@ -802,7 +802,7 @@ static int spwr_battery_register(struct spwr_battery_device *bat) if (IS_ERR(bat->psy)) return PTR_ERR(bat->psy); - return ssam_notifier_register(bat->sdev->ctrl, &bat->notif); + return ssam_device_notifier_register(bat->sdev, &bat->notif); } @@ -837,7 +837,7 @@ static void surface_battery_remove(struct ssam_device *sdev) { struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev); - ssam_notifier_unregister(sdev->ctrl, &bat->notif); + ssam_device_notifier_unregister(sdev, &bat->notif); cancel_delayed_work_sync(&bat->update_work); } diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c index a060c36c7766f..59182d55742d0 100644 --- a/drivers/power/supply/surface_charger.c +++ b/drivers/power/supply/surface_charger.c @@ -216,7 +216,7 @@ static int spwr_ac_register(struct spwr_ac_device *ac) if (IS_ERR(ac->psy)) return PTR_ERR(ac->psy); - return ssam_notifier_register(ac->sdev->ctrl, &ac->notif); + return ssam_device_notifier_register(ac->sdev, &ac->notif); } @@ -251,7 +251,7 @@ static void surface_ac_remove(struct ssam_device *sdev) { struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); - ssam_notifier_unregister(sdev->ctrl, &ac->notif); + ssam_device_notifier_unregister(sdev, &ac->notif); } static const struct spwr_psy_properties spwr_psy_props_adp1 = { diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h index 74bfdffaf7b0e..d11a1c6e3186a 100644 --- a/include/linux/surface_aggregator/controller.h +++ b/include/linux/surface_aggregator/controller.h @@ -469,6 +469,67 @@ struct ssam_request_spec_md { return 0; \ } +/** + * SSAM_DEFINE_SYNC_REQUEST_WR() - Define synchronous SAM request function with + * both argument and return value. + * @name: Name of the generated function. + * @atype: Type of the request's argument. + * @rtype: Type of the request's return value. + * @spec: Specification (&struct ssam_request_spec) defining the request. + * + * Defines a function executing the synchronous SAM request specified by @spec, + * with the request taking an argument of type @atype and having a return value + * of type @rtype. The generated function takes care of setting up the request + * and response structs, buffer allocation, as well as execution of the request + * itself, returning once the request has been fully completed. The required + * transport buffer will be allocated on the stack. + * + * The generated function is defined as ``static int name(struct + * ssam_controller *ctrl, const atype *arg, rtype *ret)``, returning the status + * of the request, which is zero on success and negative on failure. The + * ``ctrl`` parameter is the controller via which the request is sent. The + * request argument is specified via the ``arg`` pointer. The request's return + * value is written to the memory pointed to by the ``ret`` parameter. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ +#define SSAM_DEFINE_SYNC_REQUEST_WR(name, atype, rtype, spec...) \ + static int name(struct ssam_controller *ctrl, const atype *arg, rtype *ret) \ + { \ + struct ssam_request_spec s = (struct ssam_request_spec)spec; \ + struct ssam_request rqst; \ + struct ssam_response rsp; \ + int status; \ + \ + rqst.target_category = s.target_category; \ + rqst.target_id = s.target_id; \ + rqst.command_id = s.command_id; \ + rqst.instance_id = s.instance_id; \ + rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ + rqst.length = sizeof(atype); \ + rqst.payload = (u8 *)arg; \ + \ + rsp.capacity = sizeof(rtype); \ + rsp.length = 0; \ + rsp.pointer = (u8 *)ret; \ + \ + status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, sizeof(atype)); \ + if (status) \ + return status; \ + \ + if (rsp.length != sizeof(rtype)) { \ + struct device *dev = ssam_controller_device(ctrl); \ + dev_err(dev, \ + "rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \ + sizeof(rtype), rsp.length, rqst.target_category,\ + rqst.command_id); \ + return -EIO; \ + } \ + \ + return 0; \ + } + /** * SSAM_DEFINE_SYNC_REQUEST_MD_N() - Define synchronous multi-device SAM * request function with neither argument nor return value. @@ -613,6 +674,70 @@ struct ssam_request_spec_md { return 0; \ } +/** + * SSAM_DEFINE_SYNC_REQUEST_MD_WR() - Define synchronous multi-device SAM + * request function with both argument and return value. + * @name: Name of the generated function. + * @atype: Type of the request's argument. + * @rtype: Type of the request's return value. + * @spec: Specification (&struct ssam_request_spec_md) defining the request. + * + * Defines a function executing the synchronous SAM request specified by @spec, + * with the request taking an argument of type @atype and having a return value + * of type @rtype. Device specifying parameters are not hard-coded, but instead + * must be provided to the function. The generated function takes care of + * setting up the request and response structs, buffer allocation, as well as + * execution of the request itself, returning once the request has been fully + * completed. The required transport buffer will be allocated on the stack. + * + * The generated function is defined as ``static int name(struct + * ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg, rtype *ret)``, + * returning the status of the request, which is zero on success and negative + * on failure. The ``ctrl`` parameter is the controller via which the request + * is sent, ``tid`` the target ID for the request, and ``iid`` the instance ID. + * The request argument is specified via the ``arg`` pointer. The request's + * return value is written to the memory pointed to by the ``ret`` parameter. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ +#define SSAM_DEFINE_SYNC_REQUEST_MD_WR(name, atype, rtype, spec...) \ + static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, \ + const atype *arg, rtype *ret) \ + { \ + struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ + struct ssam_request rqst; \ + struct ssam_response rsp; \ + int status; \ + \ + rqst.target_category = s.target_category; \ + rqst.target_id = tid; \ + rqst.command_id = s.command_id; \ + rqst.instance_id = iid; \ + rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ + rqst.length = sizeof(atype); \ + rqst.payload = (u8 *)arg; \ + \ + rsp.capacity = sizeof(rtype); \ + rsp.length = 0; \ + rsp.pointer = (u8 *)ret; \ + \ + status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, sizeof(atype)); \ + if (status) \ + return status; \ + \ + if (rsp.length != sizeof(rtype)) { \ + struct device *dev = ssam_controller_device(ctrl); \ + dev_err(dev, \ + "rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \ + sizeof(rtype), rsp.length, rqst.target_category,\ + rqst.command_id); \ + return -EIO; \ + } \ + \ + return 0; \ + } + /* -- Event notifier/callbacks. --------------------------------------------- */ @@ -835,8 +960,28 @@ struct ssam_event_notifier { int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notifier *n); -int ssam_notifier_unregister(struct ssam_controller *ctrl, - struct ssam_event_notifier *n); +int __ssam_notifier_unregister(struct ssam_controller *ctrl, + struct ssam_event_notifier *n, bool disable); + +/** + * ssam_notifier_unregister() - Unregister an event notifier. + * @ctrl: The controller the notifier has been registered on. + * @n: The event notifier to unregister. + * + * Unregister an event notifier. Decrement the usage counter of the associated + * SAM event if the notifier is not marked as an observer. If the usage counter + * reaches zero, the event will be disabled. + * + * Return: Returns zero on success, %-ENOENT if the given notifier block has + * not been registered on the controller. If the given notifier block was the + * last one associated with its specific event, returns the status of the + * event-disable EC-command. + */ +static inline int ssam_notifier_unregister(struct ssam_controller *ctrl, + struct ssam_event_notifier *n) +{ + return __ssam_notifier_unregister(ctrl, n, true); +} int ssam_controller_event_enable(struct ssam_controller *ctrl, struct ssam_event_registry reg, diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h index cc257097eb05f..6e75fb6054790 100644 --- a/include/linux/surface_aggregator/device.h +++ b/include/linux/surface_aggregator/device.h @@ -148,17 +148,30 @@ struct ssam_device_uid { #define SSAM_SDEV(cat, tid, iid, fun) \ SSAM_DEVICE(SSAM_DOMAIN_SERIALHUB, SSAM_SSH_TC_##cat, tid, iid, fun) +/* + * enum ssam_device_flags - Flags for SSAM client devices. + * @SSAM_DEVICE_HOT_REMOVED_BIT: + * The device has been hot-removed. Further communication with it may time + * out and should be avoided. + */ +enum ssam_device_flags { + SSAM_DEVICE_HOT_REMOVED_BIT = 0, +}; + /** * struct ssam_device - SSAM client device. - * @dev: Driver model representation of the device. - * @ctrl: SSAM controller managing this device. - * @uid: UID identifying the device. + * @dev: Driver model representation of the device. + * @ctrl: SSAM controller managing this device. + * @uid: UID identifying the device. + * @flags: Device state flags, see &enum ssam_device_flags. */ struct ssam_device { struct device dev; struct ssam_controller *ctrl; struct ssam_device_uid uid; + + unsigned long flags; }; /** @@ -177,6 +190,8 @@ struct ssam_device_driver { void (*remove)(struct ssam_device *sdev); }; +#ifdef CONFIG_SURFACE_AGGREGATOR_BUS + extern struct bus_type ssam_bus_type; extern const struct device_type ssam_device_type; @@ -193,6 +208,15 @@ static inline bool is_ssam_device(struct device *d) return d->type == &ssam_device_type; } +#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ + +static inline bool is_ssam_device(struct device *d) +{ + return false; +} + +#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ + /** * to_ssam_device() - Casts the given device to a SSAM client device. * @d: The device to cast. @@ -240,6 +264,35 @@ struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, int ssam_device_add(struct ssam_device *sdev); void ssam_device_remove(struct ssam_device *sdev); +/** + * ssam_device_mark_hot_removed() - Mark the given device as hot-removed. + * @sdev: The device to mark as hot-removed. + * + * Mark the device as having been hot-removed. This signals drivers using the + * device that communication with the device should be avoided and may lead to + * timeouts. + */ +static inline void ssam_device_mark_hot_removed(struct ssam_device *sdev) +{ + dev_dbg(&sdev->dev, "marking device as hot-removed\n"); + set_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); +} + +/** + * ssam_device_is_hot_removed() - Check if the given device has been + * hot-removed. + * @sdev: The device to check. + * + * Checks if the given device has been marked as hot-removed. See + * ssam_device_mark_hot_removed() for more details. + * + * Return: Returns ``true`` if the device has been marked as hot-removed. + */ +static inline bool ssam_device_is_hot_removed(struct ssam_device *sdev) +{ + return test_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); +} + /** * ssam_device_get() - Increment reference count of SSAM client device. * @sdev: The device to increment the reference count of. @@ -322,11 +375,48 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); /* -- Helpers for controller and hub devices. ------------------------------- */ #ifdef CONFIG_SURFACE_AGGREGATOR_BUS + +int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl, + struct fwnode_handle *node); +int ssam_register_clients(struct device *dev, struct ssam_controller *ctrl); void ssam_remove_clients(struct device *dev); + #else /* CONFIG_SURFACE_AGGREGATOR_BUS */ + +static inline int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl, + struct fwnode_handle *node) +{ + return 0; +} + +static inline int ssam_register_clients(struct device *dev, struct ssam_controller *ctrl) +{ + return 0; +} + static inline void ssam_remove_clients(struct device *dev) {} + #endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ +/** + * ssam_device_register_clients() - Register all client devices defined under + * the given SSAM parent device. + * @sdev: The parent device under which clients should be registered. + * + * Register all clients that have via firmware nodes been defined as children + * of the given (parent) device. The respective child firmware nodes will be + * associated with the correspondingly created child devices. + * + * The controller used by the parent device will be used to instantiate the new + * devices. See ssam_device_add() for details. + * + * Return: Returns zero on success, nonzero on failure. + */ +static inline int ssam_device_register_clients(struct ssam_device *sdev) +{ + return ssam_register_clients(&sdev->dev, sdev->ctrl); +} + /* -- Helpers for client-device requests. ----------------------------------- */ @@ -430,4 +520,106 @@ static inline void ssam_remove_clients(struct device *dev) {} sdev->uid.instance, ret); \ } +/** + * SSAM_DEFINE_SYNC_REQUEST_CL_WR() - Define synchronous client-device SAM + * request function with argument and return value. + * @name: Name of the generated function. + * @atype: Type of the request's argument. + * @rtype: Type of the request's return value. + * @spec: Specification (&struct ssam_request_spec_md) defining the request. + * + * Defines a function executing the synchronous SAM request specified by @spec, + * with the request taking an argument of type @atype and having a return value + * of type @rtype. Device specifying parameters are not hard-coded, but instead + * are provided via the client device, specifically its UID, supplied when + * calling this function. The generated function takes care of setting up the + * request struct, buffer allocation, as well as execution of the request + * itself, returning once the request has been fully completed. The required + * transport buffer will be allocated on the stack. + * + * The generated function is defined as ``static int name(struct ssam_device + * *sdev, const atype *arg, rtype *ret)``, returning the status of the request, + * which is zero on success and negative on failure. The ``sdev`` parameter + * specifies both the target device of the request and by association the + * controller via which the request is sent. The request's argument is + * specified via the ``arg`` pointer. The request's return value is written to + * the memory pointed to by the ``ret`` parameter. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ +#define SSAM_DEFINE_SYNC_REQUEST_CL_WR(name, atype, rtype, spec...) \ + SSAM_DEFINE_SYNC_REQUEST_MD_WR(__raw_##name, atype, rtype, spec) \ + static int name(struct ssam_device *sdev, const atype *arg, rtype *ret) \ + { \ + return __raw_##name(sdev->ctrl, sdev->uid.target, \ + sdev->uid.instance, arg, ret); \ + } + + +/* -- Helpers for client-device notifiers. ---------------------------------- */ + +/** + * ssam_device_notifier_register() - Register an event notifier for the + * specified client device. + * @sdev: The device the notifier should be registered on. + * @n: The event notifier to register. + * + * Register an event notifier. Increment the usage counter of the associated + * SAM event if the notifier is not marked as an observer. If the event is not + * marked as an observer and is currently not enabled, it will be enabled + * during this call. If the notifier is marked as an observer, no attempt will + * be made at enabling any event and no reference count will be modified. + * + * Notifiers marked as observers do not need to be associated with one specific + * event, i.e. as long as no event matching is performed, only the event target + * category needs to be set. + * + * Return: Returns zero on success, %-ENOSPC if there have already been + * %INT_MAX notifiers for the event ID/type associated with the notifier block + * registered, %-ENOMEM if the corresponding event entry could not be + * allocated, %-ENODEV if the device is marked as hot-removed. If this is the + * first time that a notifier block is registered for the specific associated + * event, returns the status of the event-enable EC-command. + */ +static inline int ssam_device_notifier_register(struct ssam_device *sdev, + struct ssam_event_notifier *n) +{ + /* + * Note that this check does not provide any guarantees whatsoever as + * hot-removal could happen at any point and we can't protect against + * it. Nevertheless, if we can detect hot-removal, bail early to avoid + * communication timeouts. + */ + if (ssam_device_is_hot_removed(sdev)) + return -ENODEV; + + return ssam_notifier_register(sdev->ctrl, n); +} + +/** + * ssam_device_notifier_unregister() - Unregister an event notifier for the + * specified client device. + * @sdev: The device the notifier has been registered on. + * @n: The event notifier to unregister. + * + * Unregister an event notifier. Decrement the usage counter of the associated + * SAM event if the notifier is not marked as an observer. If the usage counter + * reaches zero, the event will be disabled. + * + * In case the device has been marked as hot-removed, the event will not be + * disabled on the EC, as in those cases any attempt at doing so may time out. + * + * Return: Returns zero on success, %-ENOENT if the given notifier block has + * not been registered on the controller. If the given notifier block was the + * last one associated with its specific event, returns the status of the + * event-disable EC-command. + */ +static inline int ssam_device_notifier_unregister(struct ssam_device *sdev, + struct ssam_event_notifier *n) +{ + return __ssam_notifier_unregister(sdev->ctrl, n, + !ssam_device_is_hot_removed(sdev)); +} + #endif /* _LINUX_SURFACE_AGGREGATOR_DEVICE_H */ diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h index c3de43edcffa8..45501b6e54e8a 100644 --- a/include/linux/surface_aggregator/serial_hub.h +++ b/include/linux/surface_aggregator/serial_hub.h @@ -201,7 +201,7 @@ static inline u16 ssh_crc(const u8 *buf, size_t len) * exception of zero, which is not an event ID. Thus, this is also the * absolute maximum number of event handlers that can be registered. */ -#define SSH_NUM_EVENTS 34 +#define SSH_NUM_EVENTS 38 /* * SSH_NUM_TARGETS - The number of communication targets used in the protocol. @@ -292,40 +292,45 @@ struct ssam_span { * Windows driver. */ enum ssam_ssh_tc { - /* Category 0x00 is invalid for EC use. */ - SSAM_SSH_TC_SAM = 0x01, /* Generic system functionality, real-time clock. */ - SSAM_SSH_TC_BAT = 0x02, /* Battery/power subsystem. */ - SSAM_SSH_TC_TMP = 0x03, /* Thermal subsystem. */ - SSAM_SSH_TC_PMC = 0x04, - SSAM_SSH_TC_FAN = 0x05, - SSAM_SSH_TC_PoM = 0x06, - SSAM_SSH_TC_DBG = 0x07, - SSAM_SSH_TC_KBD = 0x08, /* Legacy keyboard (Laptop 1/2). */ - SSAM_SSH_TC_FWU = 0x09, - SSAM_SSH_TC_UNI = 0x0a, - SSAM_SSH_TC_LPC = 0x0b, - SSAM_SSH_TC_TCL = 0x0c, - SSAM_SSH_TC_SFL = 0x0d, - SSAM_SSH_TC_KIP = 0x0e, - SSAM_SSH_TC_EXT = 0x0f, - SSAM_SSH_TC_BLD = 0x10, - SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ - SSAM_SSH_TC_SEN = 0x12, - SSAM_SSH_TC_SRQ = 0x13, - SSAM_SSH_TC_MCU = 0x14, - SSAM_SSH_TC_HID = 0x15, /* Generic HID input subsystem. */ - SSAM_SSH_TC_TCH = 0x16, - SSAM_SSH_TC_BKL = 0x17, - SSAM_SSH_TC_TAM = 0x18, - SSAM_SSH_TC_ACC = 0x19, - SSAM_SSH_TC_UFI = 0x1a, - SSAM_SSH_TC_USC = 0x1b, - SSAM_SSH_TC_PEN = 0x1c, - SSAM_SSH_TC_VID = 0x1d, - SSAM_SSH_TC_AUD = 0x1e, - SSAM_SSH_TC_SMC = 0x1f, - SSAM_SSH_TC_KPD = 0x20, - SSAM_SSH_TC_REG = 0x21, /* Extended event registry. */ + /* Category 0x00 is invalid for EC use. */ + SSAM_SSH_TC_SAM = 0x01, /* Generic system functionality, real-time clock. */ + SSAM_SSH_TC_BAT = 0x02, /* Battery/power subsystem. */ + SSAM_SSH_TC_TMP = 0x03, /* Thermal subsystem. */ + SSAM_SSH_TC_PMC = 0x04, + SSAM_SSH_TC_FAN = 0x05, + SSAM_SSH_TC_PoM = 0x06, + SSAM_SSH_TC_DBG = 0x07, + SSAM_SSH_TC_KBD = 0x08, /* Legacy keyboard (Laptop 1/2). */ + SSAM_SSH_TC_FWU = 0x09, + SSAM_SSH_TC_UNI = 0x0a, + SSAM_SSH_TC_LPC = 0x0b, + SSAM_SSH_TC_TCL = 0x0c, + SSAM_SSH_TC_SFL = 0x0d, + SSAM_SSH_TC_KIP = 0x0e, /* Manages detachable peripherals (Pro X/8 keyboard cover) */ + SSAM_SSH_TC_EXT = 0x0f, + SSAM_SSH_TC_BLD = 0x10, + SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ + SSAM_SSH_TC_SEN = 0x12, + SSAM_SSH_TC_SRQ = 0x13, + SSAM_SSH_TC_MCU = 0x14, + SSAM_SSH_TC_HID = 0x15, /* Generic HID input subsystem. */ + SSAM_SSH_TC_TCH = 0x16, + SSAM_SSH_TC_BKL = 0x17, + SSAM_SSH_TC_TAM = 0x18, + SSAM_SSH_TC_ACC0 = 0x19, + SSAM_SSH_TC_UFI = 0x1a, + SSAM_SSH_TC_USC = 0x1b, + SSAM_SSH_TC_PEN = 0x1c, + SSAM_SSH_TC_VID = 0x1d, + SSAM_SSH_TC_AUD = 0x1e, + SSAM_SSH_TC_SMC = 0x1f, + SSAM_SSH_TC_KPD = 0x20, + SSAM_SSH_TC_REG = 0x21, /* Extended event registry. */ + SSAM_SSH_TC_SPT = 0x22, + SSAM_SSH_TC_SYS = 0x23, + SSAM_SSH_TC_ACC1 = 0x24, + SSAM_SSH_TC_SHB = 0x25, + SSAM_SSH_TC_POS = 0x26, /* For obtaining Laptop Studio screen position. */ }; diff --git a/include/media/media-entity.h b/include/media/media-entity.h index 742918962d46f..1d13b8939a115 100644 --- a/include/media/media-entity.h +++ b/include/media/media-entity.h @@ -1121,4 +1121,23 @@ void media_remove_intf_links(struct media_interface *intf); (((entity)->ops && (entity)->ops->operation) ? \ (entity)->ops->operation((entity) , ##args) : -ENOIOCTLCMD) +/** + * media_create_ancillary_link() - create an ancillary link between two + * instances of &media_entity + * + * @primary: pointer to the primary &media_entity + * @ancillary: pointer to the ancillary &media_entity + * + * Create an ancillary link between two entities, indicating that they + * represent two connected pieces of hardware that form a single logical unit. + * A typical example is a camera lens controller being linked to the sensor that + * it is supporting. + * + * The function sets both MEDIA_LNK_FL_ENABLED and MEDIA_LNK_FL_IMMUTABLE for + * the new link. + */ +struct media_link * +media_create_ancillary_link(struct media_entity *primary, + struct media_entity *ancillary); + #endif diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h index 200fa8462b90c..afbae7213d35f 100644 --- a/include/uapi/linux/media.h +++ b/include/uapi/linux/media.h @@ -226,6 +226,7 @@ struct media_pad_desc { #define MEDIA_LNK_FL_LINK_TYPE (0xf << 28) # define MEDIA_LNK_FL_DATA_LINK (0 << 28) # define MEDIA_LNK_FL_INTERFACE_LINK (1 << 28) +# define MEDIA_LNK_FL_ANCILLARY_LINK (2 << 28) struct media_link_desc { struct media_pad_desc source; diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index 4b2e027c10331..dc96ec7bcbd57 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -3718,6 +3718,15 @@ static const struct dmi_system_id dmi_platform_data[] = { }, .driver_data = (void *)&intel_braswell_platform_data, }, + { + .ident = "Microsoft Surface 3", + .matches = { + DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), + DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), + DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, { /* * Match for the GPDwin which unfortunately uses somewhat diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c index 6beb00858c33f..d82d77387a0a6 100644 --- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c @@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), }, }, + { + .callback = cht_surface_quirk_cb, + .matches = { + DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), + DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), + DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), + }, + }, { } };