Commit b27622f1 authored by Konrad Dybcio's avatar Konrad Dybcio Committed by Hans de Goede

platform/surface: Add OF support

Add basic support for registering the aggregator module on Device Tree-
based platforms. These include at least three generations of Qualcomm
Snapdragon-based Surface devices:

- SC8180X / SQ1 / SQ2: Pro X,
- SC8280XP / SQ3: Devkit 2023, Pro 9
- X Elite: Laptop 7 / Pro11

Thankfully, the aggregators on these seem to be configured in an
identical way, which allows for using these settings as defaults and
no DT properties need to be introduced (until that changes, anyway).

Based on the work done by Maximilian Luz, largely rewritten.
Signed-off-by: default avatarKonrad Dybcio <quic_kdybcio@quicinc.com>
Reviewed-by: default avatarMaximilian Luz <luzmaximilian@gmail.com>
Tested-by: default avatarMaximilian Luz <luzmaximilian@gmail.com>
Link: https://lore.kernel.org/r/20240814-topic-sam-v3-3-a84588aad233@quicinc.comReviewed-by: default avatarHans de Goede <hdegoede@redhat.com>
Signed-off-by: default avatarHans de Goede <hdegoede@redhat.com>
parent ceccd196
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
*/ */
#include <linux/device.h> #include <linux/device.h>
#include <linux/of.h>
#include <linux/property.h> #include <linux/property.h>
#include <linux/slab.h> #include <linux/slab.h>
...@@ -441,6 +442,7 @@ static int ssam_add_client_device(struct device *parent, struct ssam_controller ...@@ -441,6 +442,7 @@ static int ssam_add_client_device(struct device *parent, struct ssam_controller
sdev->dev.parent = parent; sdev->dev.parent = parent;
sdev->dev.fwnode = fwnode_handle_get(node); sdev->dev.fwnode = fwnode_handle_get(node);
sdev->dev.of_node = to_of_node(node);
status = ssam_device_add(sdev); status = ssam_device_add(sdev);
if (status) if (status)
......
...@@ -1104,13 +1104,6 @@ int ssam_controller_caps_load_from_acpi(acpi_handle handle, ...@@ -1104,13 +1104,6 @@ int ssam_controller_caps_load_from_acpi(acpi_handle handle,
u64 funcs; u64 funcs;
int status; int status;
/* Set defaults. */
caps->ssh_power_profile = U32_MAX;
caps->screen_on_sleep_idle_timeout = U32_MAX;
caps->screen_off_sleep_idle_timeout = U32_MAX;
caps->d3_closes_handle = false;
caps->ssh_buffer_size = U32_MAX;
/* Pre-load supported DSM functions. */ /* Pre-load supported DSM functions. */
status = ssam_dsm_get_functions(handle, &funcs); status = ssam_dsm_get_functions(handle, &funcs);
if (status) if (status)
...@@ -1149,6 +1142,52 @@ int ssam_controller_caps_load_from_acpi(acpi_handle handle, ...@@ -1149,6 +1142,52 @@ int ssam_controller_caps_load_from_acpi(acpi_handle handle,
return 0; return 0;
} }
/**
* ssam_controller_caps_load_from_of() - Load controller capabilities from OF/DT.
* @dev: A pointer to the controller device
* @caps: Where to store the capabilities in.
*
* Return: Returns zero on success, a negative error code on failure.
*/
static int ssam_controller_caps_load_from_of(struct device *dev, struct ssam_controller_caps *caps)
{
/*
* Every device starting with Surface Pro X through Laptop 7 uses these
* identical values, which makes them good defaults.
*/
caps->d3_closes_handle = true;
caps->screen_on_sleep_idle_timeout = 5000;
caps->screen_off_sleep_idle_timeout = 30;
caps->ssh_buffer_size = 48;
/* TODO: figure out power profile */
return 0;
}
/**
* ssam_controller_caps_load() - Load controller capabilities
* @dev: A pointer to the controller device
* @caps: Where to store the capabilities in.
*
* Return: Returns zero on success, a negative error code on failure.
*/
static int ssam_controller_caps_load(struct device *dev, struct ssam_controller_caps *caps)
{
acpi_handle handle = ACPI_HANDLE(dev);
/* Set defaults. */
caps->ssh_power_profile = U32_MAX;
caps->screen_on_sleep_idle_timeout = U32_MAX;
caps->screen_off_sleep_idle_timeout = U32_MAX;
caps->d3_closes_handle = false;
caps->ssh_buffer_size = U32_MAX;
if (handle)
return ssam_controller_caps_load_from_acpi(handle, caps);
else
return ssam_controller_caps_load_from_of(dev, caps);
}
/** /**
* ssam_controller_init() - Initialize SSAM controller. * ssam_controller_init() - Initialize SSAM controller.
* @ctrl: The controller to initialize. * @ctrl: The controller to initialize.
...@@ -1165,13 +1204,12 @@ int ssam_controller_caps_load_from_acpi(acpi_handle handle, ...@@ -1165,13 +1204,12 @@ int ssam_controller_caps_load_from_acpi(acpi_handle handle,
int ssam_controller_init(struct ssam_controller *ctrl, int ssam_controller_init(struct ssam_controller *ctrl,
struct serdev_device *serdev) struct serdev_device *serdev)
{ {
acpi_handle handle = ACPI_HANDLE(&serdev->dev);
int status; int status;
init_rwsem(&ctrl->lock); init_rwsem(&ctrl->lock);
kref_init(&ctrl->kref); kref_init(&ctrl->kref);
status = ssam_controller_caps_load_from_acpi(handle, &ctrl->caps); status = ssam_controller_caps_load(&serdev->dev, &ctrl->caps);
if (status) if (status)
return status; return status;
...@@ -2715,11 +2753,12 @@ int ssam_irq_setup(struct ssam_controller *ctrl) ...@@ -2715,11 +2753,12 @@ int ssam_irq_setup(struct ssam_controller *ctrl)
const int irqf = IRQF_ONESHOT | IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN; const int irqf = IRQF_ONESHOT | IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN;
gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS); gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS);
if (IS_ERR(gpiod)) if (IS_ERR(gpiod)) {
return PTR_ERR(gpiod); irq = fwnode_irq_get(dev_fwnode(dev), 0);
} else {
irq = gpiod_to_irq(gpiod); irq = gpiod_to_irq(gpiod);
gpiod_put(gpiod); gpiod_put(gpiod);
}
if (irq < 0) if (irq < 0)
return irq; return irq;
......
...@@ -17,9 +17,12 @@ ...@@ -17,9 +17,12 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/kref.h> #include <linux/kref.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm.h> #include <linux/pm.h>
#include <linux/serdev.h> #include <linux/serdev.h>
#include <linux/sysfs.h> #include <linux/sysfs.h>
#include <linux/units.h>
#include <linux/surface_aggregator/controller.h> #include <linux/surface_aggregator/controller.h>
#include <linux/surface_aggregator/device.h> #include <linux/surface_aggregator/device.h>
...@@ -299,7 +302,7 @@ static const struct attribute_group ssam_sam_group = { ...@@ -299,7 +302,7 @@ static const struct attribute_group ssam_sam_group = {
}; };
/* -- ACPI based device setup. ---------------------------------------------- */ /* -- Serial device setup. -------------------------------------------------- */
static acpi_status ssam_serdev_setup_via_acpi_crs(struct acpi_resource *rsc, static acpi_status ssam_serdev_setup_via_acpi_crs(struct acpi_resource *rsc,
void *ctx) void *ctx)
...@@ -352,13 +355,28 @@ static acpi_status ssam_serdev_setup_via_acpi_crs(struct acpi_resource *rsc, ...@@ -352,13 +355,28 @@ static acpi_status ssam_serdev_setup_via_acpi_crs(struct acpi_resource *rsc,
return AE_CTRL_TERMINATE; return AE_CTRL_TERMINATE;
} }
static acpi_status ssam_serdev_setup_via_acpi(acpi_handle handle, static int ssam_serdev_setup_via_acpi(struct serdev_device *serdev, acpi_handle handle)
struct serdev_device *serdev)
{ {
return acpi_walk_resources(handle, METHOD_NAME__CRS, acpi_status status;
ssam_serdev_setup_via_acpi_crs, serdev);
status = acpi_walk_resources(handle, METHOD_NAME__CRS,
ssam_serdev_setup_via_acpi_crs, serdev);
return status ? -ENXIO : 0;
} }
static int ssam_serdev_setup(struct acpi_device *ssh, struct serdev_device *serdev)
{
if (ssh)
return ssam_serdev_setup_via_acpi(serdev, ssh->handle);
/* TODO: these values may differ per board/implementation */
serdev_device_set_baudrate(serdev, 4 * HZ_PER_MHZ);
serdev_device_set_flow_control(serdev, true);
serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
return 0;
}
/* -- Power management. ----------------------------------------------------- */ /* -- Power management. ----------------------------------------------------- */
...@@ -621,16 +639,17 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) ...@@ -621,16 +639,17 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev)
struct device *dev = &serdev->dev; struct device *dev = &serdev->dev;
struct acpi_device *ssh = ACPI_COMPANION(dev); struct acpi_device *ssh = ACPI_COMPANION(dev);
struct ssam_controller *ctrl; struct ssam_controller *ctrl;
acpi_status astatus;
int status; int status;
status = gpiod_count(dev, NULL); if (ssh) {
if (status < 0) status = gpiod_count(dev, NULL);
return dev_err_probe(dev, status, "no GPIO found\n"); if (status < 0)
return dev_err_probe(dev, status, "no GPIO found\n");
status = devm_acpi_dev_add_driver_gpios(dev, ssam_acpi_gpios); status = devm_acpi_dev_add_driver_gpios(dev, ssam_acpi_gpios);
if (status) if (status)
return status; return status;
}
/* Allocate controller. */ /* Allocate controller. */
ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL);
...@@ -655,9 +674,9 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) ...@@ -655,9 +674,9 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev)
goto err_devopen; goto err_devopen;
} }
astatus = ssam_serdev_setup_via_acpi(ssh->handle, serdev); status = ssam_serdev_setup(ssh, serdev);
if (ACPI_FAILURE(astatus)) { if (status) {
status = dev_err_probe(dev, -ENXIO, "failed to setup serdev\n"); status = dev_err_probe(dev, status, "failed to setup serdev\n");
goto err_devinit; goto err_devinit;
} }
...@@ -717,7 +736,23 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) ...@@ -717,7 +736,23 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev)
* For now let's thus default power/wakeup to false. * For now let's thus default power/wakeup to false.
*/ */
device_set_wakeup_capable(dev, true); device_set_wakeup_capable(dev, true);
acpi_dev_clear_dependencies(ssh);
/*
* When using DT, we have to register the platform hub driver manually,
* as it can't be matched based on top-level board compatible (like it
* does the ACPI case).
*/
if (!ssh) {
struct platform_device *ph_pdev =
platform_device_register_simple("surface_aggregator_platform_hub",
0, NULL, 0);
if (IS_ERR(ph_pdev))
return dev_err_probe(dev, PTR_ERR(ph_pdev),
"Failed to register the platform hub driver\n");
}
if (ssh)
acpi_dev_clear_dependencies(ssh);
return 0; return 0;
...@@ -782,18 +817,27 @@ static void ssam_serial_hub_remove(struct serdev_device *serdev) ...@@ -782,18 +817,27 @@ static void ssam_serial_hub_remove(struct serdev_device *serdev)
device_set_wakeup_capable(&serdev->dev, false); device_set_wakeup_capable(&serdev->dev, false);
} }
static const struct acpi_device_id ssam_serial_hub_match[] = { static const struct acpi_device_id ssam_serial_hub_acpi_match[] = {
{ "MSHW0084", 0 }, { "MSHW0084", 0 },
{ }, { },
}; };
MODULE_DEVICE_TABLE(acpi, ssam_serial_hub_match); MODULE_DEVICE_TABLE(acpi, ssam_serial_hub_acpi_match);
#ifdef CONFIG_OF
static const struct of_device_id ssam_serial_hub_of_match[] = {
{ .compatible = "microsoft,surface-sam", },
{ },
};
MODULE_DEVICE_TABLE(of, ssam_serial_hub_of_match);
#endif
static struct serdev_device_driver ssam_serial_hub = { static struct serdev_device_driver ssam_serial_hub = {
.probe = ssam_serial_hub_probe, .probe = ssam_serial_hub_probe,
.remove = ssam_serial_hub_remove, .remove = ssam_serial_hub_remove,
.driver = { .driver = {
.name = "surface_serial_hub", .name = "surface_serial_hub",
.acpi_match_table = ssam_serial_hub_match, .acpi_match_table = ACPI_PTR(ssam_serial_hub_acpi_match),
.of_match_table = of_match_ptr(ssam_serial_hub_of_match),
.pm = &ssam_serial_hub_pm_ops, .pm = &ssam_serial_hub_pm_ops,
.shutdown = ssam_serial_hub_shutdown, .shutdown = ssam_serial_hub_shutdown,
.probe_type = PROBE_PREFER_ASYNCHRONOUS, .probe_type = PROBE_PREFER_ASYNCHRONOUS,
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include <linux/acpi.h> #include <linux/acpi.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/property.h> #include <linux/property.h>
#include <linux/types.h> #include <linux/types.h>
...@@ -273,6 +274,18 @@ static const struct software_node *ssam_node_group_sl5[] = { ...@@ -273,6 +274,18 @@ static const struct software_node *ssam_node_group_sl5[] = {
NULL, NULL,
}; };
/* Devices for Surface Laptop 7. */
static const struct software_node *ssam_node_group_sl7[] = {
&ssam_node_root,
&ssam_node_bat_ac,
&ssam_node_bat_main,
&ssam_node_tmp_perf_profile_with_fan,
&ssam_node_fan_speed,
&ssam_node_hid_sam_keyboard,
/* TODO: evaluate thermal sensors devices when we get a driver for that */
NULL,
};
/* Devices for Surface Laptop Studio. */ /* Devices for Surface Laptop Studio. */
static const struct software_node *ssam_node_group_sls[] = { static const struct software_node *ssam_node_group_sls[] = {
&ssam_node_root, &ssam_node_root,
...@@ -346,7 +359,7 @@ static const struct software_node *ssam_node_group_sp9[] = { ...@@ -346,7 +359,7 @@ static const struct software_node *ssam_node_group_sp9[] = {
/* -- SSAM platform/meta-hub driver. ---------------------------------------- */ /* -- SSAM platform/meta-hub driver. ---------------------------------------- */
static const struct acpi_device_id ssam_platform_hub_match[] = { static const struct acpi_device_id ssam_platform_hub_acpi_match[] = {
/* Surface Pro 4, 5, and 6 (OMBR < 0x10) */ /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */
{ "MSHW0081", (unsigned long)ssam_node_group_gen5 }, { "MSHW0081", (unsigned long)ssam_node_group_gen5 },
...@@ -400,18 +413,39 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { ...@@ -400,18 +413,39 @@ static const struct acpi_device_id ssam_platform_hub_match[] = {
{ }, { },
}; };
MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_match); MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_acpi_match);
static const struct of_device_id ssam_platform_hub_of_match[] __maybe_unused = {
/* Surface Laptop 7 */
{ .compatible = "microsoft,romulus13", (void *)ssam_node_group_sl7 },
{ .compatible = "microsoft,romulus15", (void *)ssam_node_group_sl7 },
{ },
};
static int ssam_platform_hub_probe(struct platform_device *pdev) static int ssam_platform_hub_probe(struct platform_device *pdev)
{ {
const struct software_node **nodes; const struct software_node **nodes;
const struct of_device_id *match;
struct device_node *fdt_root;
struct ssam_controller *ctrl; struct ssam_controller *ctrl;
struct fwnode_handle *root; struct fwnode_handle *root;
int status; int status;
nodes = (const struct software_node **)acpi_device_get_match_data(&pdev->dev); nodes = (const struct software_node **)acpi_device_get_match_data(&pdev->dev);
if (!nodes) if (!nodes) {
return -ENODEV; fdt_root = of_find_node_by_path("/");
if (!fdt_root)
return -ENODEV;
match = of_match_node(ssam_platform_hub_of_match, fdt_root);
of_node_put(fdt_root);
if (!match)
return -ENODEV;
nodes = (const struct software_node **)match->data;
if (!nodes)
return -ENODEV;
}
/* /*
* As we're adding the SSAM client devices as children under this device * As we're adding the SSAM client devices as children under this device
...@@ -460,12 +494,13 @@ static struct platform_driver ssam_platform_hub_driver = { ...@@ -460,12 +494,13 @@ static struct platform_driver ssam_platform_hub_driver = {
.remove_new = ssam_platform_hub_remove, .remove_new = ssam_platform_hub_remove,
.driver = { .driver = {
.name = "surface_aggregator_platform_hub", .name = "surface_aggregator_platform_hub",
.acpi_match_table = ssam_platform_hub_match, .acpi_match_table = ssam_platform_hub_acpi_match,
.probe_type = PROBE_PREFER_ASYNCHRONOUS, .probe_type = PROBE_PREFER_ASYNCHRONOUS,
}, },
}; };
module_platform_driver(ssam_platform_hub_driver); module_platform_driver(ssam_platform_hub_driver);
MODULE_ALIAS("platform:surface_aggregator_platform_hub");
MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module"); MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment