| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * dptf_power: DPTF platform power driver |
| * Copyright (c) 2016, Intel Corporation. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/acpi.h> |
| #include <linux/platform_device.h> |
| |
| /* |
| * Presentation of attributes which are defined for INT3407 and INT3532. |
| * They are: |
| * PMAX : Maximum platform power |
| * PSRC : Platform power source |
| * ARTG : Adapter rating |
| * CTYP : Charger type |
| * PROP : Rest of worst case platform Power |
| * PBSS : Power Battery Steady State |
| * RBHF : High Frequency Impedance |
| * VBNL : Instantaneous No-Load Voltage |
| * CMPP : Current Discharge Capability |
| */ |
| #define DPTF_POWER_SHOW(name, object) \ |
| static ssize_t name##_show(struct device *dev,\ |
| struct device_attribute *attr,\ |
| char *buf)\ |
| {\ |
| struct acpi_device *acpi_dev = dev_get_drvdata(dev);\ |
| unsigned long long val;\ |
| acpi_status status;\ |
| \ |
| status = acpi_evaluate_integer(acpi_dev->handle, #object,\ |
| NULL, &val);\ |
| if (ACPI_SUCCESS(status))\ |
| return sprintf(buf, "%d\n", (int)val);\ |
| else \ |
| return -EINVAL;\ |
| } |
| |
| DPTF_POWER_SHOW(max_platform_power_mw, PMAX) |
| DPTF_POWER_SHOW(platform_power_source, PSRC) |
| DPTF_POWER_SHOW(adapter_rating_mw, ARTG) |
| DPTF_POWER_SHOW(battery_steady_power_mw, PBSS) |
| DPTF_POWER_SHOW(charger_type, CTYP) |
| DPTF_POWER_SHOW(rest_of_platform_power_mw, PROP) |
| DPTF_POWER_SHOW(max_steady_state_power_mw, PBSS) |
| DPTF_POWER_SHOW(high_freq_impedance_mohm, RBHF) |
| DPTF_POWER_SHOW(no_load_voltage_mv, VBNL) |
| DPTF_POWER_SHOW(current_discharge_capbility_ma, CMPP); |
| |
| static DEVICE_ATTR_RO(max_platform_power_mw); |
| static DEVICE_ATTR_RO(platform_power_source); |
| static DEVICE_ATTR_RO(adapter_rating_mw); |
| static DEVICE_ATTR_RO(battery_steady_power_mw); |
| static DEVICE_ATTR_RO(charger_type); |
| static DEVICE_ATTR_RO(rest_of_platform_power_mw); |
| static DEVICE_ATTR_RO(max_steady_state_power_mw); |
| static DEVICE_ATTR_RO(high_freq_impedance_mohm); |
| static DEVICE_ATTR_RO(no_load_voltage_mv); |
| static DEVICE_ATTR_RO(current_discharge_capbility_ma); |
| |
| static ssize_t prochot_confirm_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct acpi_device *acpi_dev = dev_get_drvdata(dev); |
| acpi_status status; |
| int seq_no; |
| |
| if (kstrtouint(buf, 0, &seq_no) < 0) |
| return -EINVAL; |
| |
| status = acpi_execute_simple_method(acpi_dev->handle, "PBOK", seq_no); |
| if (ACPI_SUCCESS(status)) |
| return count; |
| |
| return -EINVAL; |
| } |
| |
| static DEVICE_ATTR_WO(prochot_confirm); |
| |
| static struct attribute *dptf_power_attrs[] = { |
| &dev_attr_max_platform_power_mw.attr, |
| &dev_attr_platform_power_source.attr, |
| &dev_attr_adapter_rating_mw.attr, |
| &dev_attr_battery_steady_power_mw.attr, |
| &dev_attr_charger_type.attr, |
| &dev_attr_rest_of_platform_power_mw.attr, |
| &dev_attr_prochot_confirm.attr, |
| NULL |
| }; |
| |
| static const struct attribute_group dptf_power_attribute_group = { |
| .attrs = dptf_power_attrs, |
| .name = "dptf_power" |
| }; |
| |
| static struct attribute *dptf_battery_attrs[] = { |
| &dev_attr_max_platform_power_mw.attr, |
| &dev_attr_max_steady_state_power_mw.attr, |
| &dev_attr_high_freq_impedance_mohm.attr, |
| &dev_attr_no_load_voltage_mv.attr, |
| &dev_attr_current_discharge_capbility_ma.attr, |
| NULL |
| }; |
| |
| static const struct attribute_group dptf_battery_attribute_group = { |
| .attrs = dptf_battery_attrs, |
| .name = "dptf_battery" |
| }; |
| |
| #define MAX_POWER_CHANGED 0x80 |
| #define POWER_STATE_CHANGED 0x81 |
| #define STEADY_STATE_POWER_CHANGED 0x83 |
| #define POWER_PROP_CHANGE_EVENT 0x84 |
| #define IMPEDANCE_CHANGED 0x85 |
| #define VOLTAGE_CURRENT_CHANGED 0x86 |
| |
| static long long dptf_participant_type(acpi_handle handle) |
| { |
| unsigned long long ptype; |
| acpi_status status; |
| |
| status = acpi_evaluate_integer(handle, "PTYP", NULL, &ptype); |
| if (ACPI_FAILURE(status)) |
| return -ENODEV; |
| |
| return ptype; |
| } |
| |
| static void dptf_power_notify(acpi_handle handle, u32 event, void *data) |
| { |
| struct platform_device *pdev = data; |
| char *attr; |
| |
| switch (event) { |
| case POWER_STATE_CHANGED: |
| attr = "platform_power_source"; |
| break; |
| case POWER_PROP_CHANGE_EVENT: |
| attr = "rest_of_platform_power_mw"; |
| break; |
| case MAX_POWER_CHANGED: |
| attr = "max_platform_power_mw"; |
| break; |
| case STEADY_STATE_POWER_CHANGED: |
| attr = "max_steady_state_power_mw"; |
| break; |
| case IMPEDANCE_CHANGED: |
| attr = "high_freq_impedance_mohm"; |
| break; |
| case VOLTAGE_CURRENT_CHANGED: |
| attr = "no_load_voltage_mv"; |
| break; |
| default: |
| dev_err(&pdev->dev, "Unsupported event [0x%x]\n", event); |
| return; |
| } |
| |
| /* |
| * Notify that an attribute is changed, so that user space can read |
| * again. |
| */ |
| if (dptf_participant_type(handle) == 0x0CULL) |
| sysfs_notify(&pdev->dev.kobj, "dptf_battery", attr); |
| else |
| sysfs_notify(&pdev->dev.kobj, "dptf_power", attr); |
| } |
| |
| static int dptf_power_add(struct platform_device *pdev) |
| { |
| const struct attribute_group *attr_group; |
| struct acpi_device *acpi_dev; |
| unsigned long long ptype; |
| int result; |
| |
| acpi_dev = ACPI_COMPANION(&(pdev->dev)); |
| if (!acpi_dev) |
| return -ENODEV; |
| |
| ptype = dptf_participant_type(acpi_dev->handle); |
| if (ptype == 0x11) |
| attr_group = &dptf_power_attribute_group; |
| else if (ptype == 0x0C) |
| attr_group = &dptf_battery_attribute_group; |
| else |
| return -ENODEV; |
| |
| result = acpi_install_notify_handler(acpi_dev->handle, |
| ACPI_DEVICE_NOTIFY, |
| dptf_power_notify, |
| (void *)pdev); |
| if (result) |
| return result; |
| |
| result = sysfs_create_group(&pdev->dev.kobj, |
| attr_group); |
| if (result) { |
| acpi_remove_notify_handler(acpi_dev->handle, |
| ACPI_DEVICE_NOTIFY, |
| dptf_power_notify); |
| return result; |
| } |
| |
| platform_set_drvdata(pdev, acpi_dev); |
| |
| return 0; |
| } |
| |
| static void dptf_power_remove(struct platform_device *pdev) |
| { |
| struct acpi_device *acpi_dev = platform_get_drvdata(pdev); |
| |
| acpi_remove_notify_handler(acpi_dev->handle, |
| ACPI_DEVICE_NOTIFY, |
| dptf_power_notify); |
| |
| if (dptf_participant_type(acpi_dev->handle) == 0x0CULL) |
| sysfs_remove_group(&pdev->dev.kobj, &dptf_battery_attribute_group); |
| else |
| sysfs_remove_group(&pdev->dev.kobj, &dptf_power_attribute_group); |
| } |
| |
| static const struct acpi_device_id int3407_device_ids[] = { |
| {"INT3407", 0}, |
| {"INT3532", 0}, |
| {"INTC1047", 0}, |
| {"INTC1050", 0}, |
| {"INTC1060", 0}, |
| {"INTC1061", 0}, |
| {"INTC1065", 0}, |
| {"INTC1066", 0}, |
| {"INTC10A4", 0}, |
| {"INTC10A5", 0}, |
| {"", 0}, |
| }; |
| MODULE_DEVICE_TABLE(acpi, int3407_device_ids); |
| |
| static struct platform_driver dptf_power_driver = { |
| .probe = dptf_power_add, |
| .remove_new = dptf_power_remove, |
| .driver = { |
| .name = "dptf_power", |
| .acpi_match_table = int3407_device_ids, |
| }, |
| }; |
| |
| module_platform_driver(dptf_power_driver); |
| |
| MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("ACPI DPTF platform power driver"); |