From: Guenter Roeck <linux@roeck-us.net>
To: "Joaquín Ignacio Aramendía" <samsagax@gmail.com>, hdegoede@redhat.com
Cc: markgross@kernel.org, jdelvare@suse.com,
platform-driver-x86@vger.kernel.org, linux-hwmon@vger.kernel.org
Subject: Re: [PATCH] Add OneXPlayer mini AMD board driver
Date: Sat, 29 Oct 2022 16:30:55 -0700 [thread overview]
Message-ID: <506a6e7f-4566-2dcf-37f3-0f41f4ce983b@roeck-us.net> (raw)
In-Reply-To: <20221029225051.1171795-1-samsagax@gmail.com>
On 10/29/22 15:50, Joaquín Ignacio Aramendía wrote:
> Platform driver for OXP Handhelds that expose fan reading and control
> via hwmon sysfs.
>
> As far as I could gather all OXP boards have the same DMI strings and
> they are told appart by the boot cpu vendor (Intel/AMD).
> Currently only AMD boards are supported but the code is made to be simple
> to add other handheld boards in the future.
>
> Fan control is provided via pwm interface in the range [0-255]. AMD
> boards have [0-100] as range in the EC, the written value is scaled to
> accomodate for that.
>
> PWM control is disabled by default, can be enabled via module parameter
> `fan_control=1`.
> ---
> drivers/platform/x86/Kconfig | 8 +
> drivers/platform/x86/Makefile | 3 +
> drivers/platform/x86/oxp-platform.c | 393 ++++++++++++++++++++++++++++
> 3 files changed, 404 insertions(+)
> create mode 100644 drivers/platform/x86/oxp-platform.c
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index f5312f51de19..8fe3ca1dd808 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -738,6 +738,14 @@ config XO1_RFKILL
> Support for enabling/disabling the WLAN interface on the OLPC XO-1
> laptop.
>
> +config OXP_DEVICE
> + tristate "OneXPlayer EC fan control"
> + depends on ACPI
> + depends on HWMON
> + help
> + Support for OneXPlayer device board EC fan control. Only AMD boards
> + are supported.
> +
> config PCENGINES_APU2
> tristate "PC Engines APUv2/3 front button and LEDs driver"
> depends on INPUT && INPUT_KEYBOARD && GPIOLIB
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index 5a428caa654a..fa6f5c68ec45 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -80,6 +80,9 @@ obj-$(CONFIG_MSI_WMI) += msi-wmi.o
> obj-$(CONFIG_XO15_EBOOK) += xo15-ebook.o
> obj-$(CONFIG_XO1_RFKILL) += xo1-rfkill.o
>
> +# OneXPlayer
> +obj-$(CONFIG_OXP_DEVICE) += oxp-platform.o
> +
> # PC Engines
> obj-$(CONFIG_PCENGINES_APU2) += pcengines-apuv2.o
>
> diff --git a/drivers/platform/x86/oxp-platform.c b/drivers/platform/x86/oxp-platform.c
> new file mode 100644
> index 000000000000..4fb13e7450ff
> --- /dev/null
> +++ b/drivers/platform/x86/oxp-platform.c
> @@ -0,0 +1,393 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Platform driver for OXP Handhelds that expose fan reading and control
> + * via hwmon sysfs.
> + *
> + * All boards have the same DMI strings and they are told appart by the
> + * boot cpu vendor (Intel/AMD). Currently only AMD boards are supported
> + * but the code is made to be simple to add other handheld boards in the
> + * future.
> + * Fan control is provided via pwm interface in the range [0-255]. AMD
> + * boards use [0-100] as range in the EC, the written value is scaled to
> + * accomodate for that.
> + *
> + * PWM control is disabled by default, can be enabled via module parameter.
> + *
> + * Copyright (C) 2022 Joaquín I. Aramendía <samsagax@gmail.com>
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/dev_printk.h>
> +#include <linux/dmi.h>
> +#include <linux/hwmon.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <asm/processor.h>
> +
> +static bool fan_control;
> +module_param_hw(fan_control, bool, other, 0644);
Runtime writeable parameter is unacceptable. Why would this be needed anyway ?
What is it supposed to accomplish that can not be done with a sysfs attribute ?
> +MODULE_PARM_DESC(fan_control, "Enable fan control");
> +
> +#define ACPI_LOCK_DELAY_MS 500
> +
> +/* Handle ACPI lock mechanism */
> +struct lock_data {
> + u32 mutex;
> + bool (*lock)(struct lock_data *data);
> + bool (*unlock)(struct lock_data *data);
> +};
> +
> +static bool lock_global_acpi_lock(struct lock_data *data)
> +{
> + return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS,
> + &data->mutex));
> +}
> +
> +static bool unlock_global_acpi_lock(struct lock_data *data)
> +{
> + return ACPI_SUCCESS(acpi_release_global_lock(data->mutex));
> +}
> +
> +#define MAX_IDENTICAL_BOARD_VARIATIONS 2
> +
> +enum board_family {
> + family_unknown,
> + family_mini_amd,
> +};
> +
> +enum oxp_sensor_type {
> + oxp_sensor_fan = 0,
> + oxp_sensor_pwm,
> + oxp_sensor_number,
> +};
> +
> +struct oxp_ec_sensor_addr {
> + enum hwmon_sensor_types type;
> + u8 reg;
> + short size;
> + union {
> + struct {
> + u8 enable;
> + u8 val_enable;
> + u8 val_disable;
> + };
> + struct {
> + int max_speed;
> + };
> + };
> +};
> +
> +
Extra empty line.
> +/* AMD board EC addresses */
> +static const struct oxp_ec_sensor_addr amd_sensors[] = {
> + [oxp_sensor_fan] = {
> + .type = hwmon_fan,
> + .reg = 0x76,
> + .size = 2,
> + .max_speed = 5000,
> + },
> + [oxp_sensor_pwm] = {
> + .type = hwmon_pwm,
> + .reg = 0x4B,
> + .size = 1,
> + .enable = 0x4A,
> + .val_enable = 0x01,
> + .val_disable = 0x00,
> + },
I don't see the point of this data structure. There is just one
entry. Why not use defines ?
> + {},
> +};
> +
> +struct ec_board_info {
> + const char *board_names[MAX_IDENTICAL_BOARD_VARIATIONS];
> + enum board_family family;
> + const struct oxp_ec_sensor_addr *sensors;
> +};
> +
> +static const struct ec_board_info board_info[] = {
> + {
> + .board_names = {"ONE XPLAYER", "ONEXPLAYER mini A07"},
> + .family = family_mini_amd,
> + .sensors = amd_sensors,
> + },
There is just one family. What is the point of this data structure ?
> + {}
> +};
> +
> +struct oxp_status {
> + struct ec_board_info board;
> + struct lock_data lock_data;
> +};
> +
> +/* Helper functions */
> +static int read_from_ec(u8 reg, int size, long *val)
> +{
> + int i;
> + int ret;
> + u8 buffer;
> +
> + *val = 0;
> + for (i = 0; i < size; i++) {
> + ret = ec_read(reg + i, &buffer);
> + if (ret)
> + return ret;
> + (*val) <<= i*8;
space between i and 8
> + *val += buffer;
> + }
> + return ret;
return 0;
> +}
> +
> +static int write_to_ec(const struct device *dev, u8 reg, u8 value)
> +{
> + struct oxp_status *state = dev_get_drvdata(dev);
> + int ret = -1;
unnecessary (and bad) variable initialization
> +
> + if (!state->lock_data.lock(&state->lock_data)) {
> + dev_warn(dev, "Failed to acquire mutex");
> + return -EBUSY;
> + }
> +
> + ret = ec_write(reg, value);
> +
> + if (!state->lock_data.unlock(&state->lock_data))
> + dev_err(dev, "Failed to release mutex");
> +
> + return ret;
> +}
> +
> +/* Callbacks for hwmon interface */
> +static umode_t oxp_ec_hwmon_is_visible(const void *drvdata,
> + enum hwmon_sensor_types type, u32 attr, int channel)
> +{
> + switch (type) {
> + case hwmon_fan:
> + return S_IRUGO;
> + case hwmon_pwm:
> + return S_IRUGO | S_IWUSR;
Please use 0444 and 0644 directly. Checkpatch will tell.
> + default:
> + return 0;
> + }
> + return 0;
> +}
> +
> +static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long *val)
Align continuation line with '('. Checkpatch will tell.
> +{
> + int ret = -1;
> + const struct oxp_status *state = dev_get_drvdata(dev);
> + const struct ec_board_info *board = &state->board;
> +
> + switch(type) {
> + case hwmon_fan:
> + switch(attr) {
> + case hwmon_fan_input:
> + ret = read_from_ec(board->sensors[oxp_sensor_fan].reg,
> + board->sensors[oxp_sensor_fan].size, val);
> + break;
> + case hwmon_fan_max:
> + ret = 0;
> + *val = board->sensors[oxp_sensor_fan].max_speed;
> + break;
> + case hwmon_fan_min:
> + ret = 0;
> + *val = 0;
> + break;
If fan_max and fan_min are not sent to/from the EC, do not provide the attributes.
> + default:
> + dev_dbg(dev, "Unknown attribute for type %d: %d\n", type, attr);
missing break;
> + }
> + return ret;
> + case hwmon_pwm:
> + switch(attr) {
> + case hwmon_pwm_input:
> + ret = read_from_ec(board->sensors[oxp_sensor_pwm].reg,
> + board->sensors[oxp_sensor_pwm].size, val);
> + if (board->family == family_mini_amd) {
> + *val = (*val * 255) / 100;
> + }
> + break;
> + case hwmon_pwm_enable:
> + ret = read_from_ec(board->sensors[oxp_sensor_pwm].enable, 1, val);
> + if (*val == board->sensors[oxp_sensor_pwm].val_enable) {
> + *val = 1;
> + } else {
> + *val = 0;
> + }
Unnecessary { }. Checkpatch would tell.
I don't see the point of this code. Why have board->sensors[oxp_sensor_pwm].val_enable
to start with ? It is 1. Can the EC return something else ? If so, what is the
rationale to map it to 0 ?
> + break;
> + default:
> + dev_dbg(dev, "Unknown attribute for type %d: %d\n", type, attr);
missing break;
> + }
> + return ret;
> + default:
> + dev_dbg(dev, "Unknown sensor type %d.\n", type);
> + return -1;
bad error code.
> + }
> +}
> +
> +static int oxp_pwm_enable(const struct device *dev)
> +{
> + int ret = -1;
Unnecessary (and bad) variable initialization.
> + struct oxp_status *state = dev_get_drvdata(dev);
> + const struct ec_board_info *board = &state->board;
> +
> + if (!fan_control) {
> + dev_info(dev, "Can't enable pwm, fan_control=0");
> + return -EPERM;
> + }
> +
> + ret = write_to_ec(dev, board->sensors[oxp_sensor_pwm].enable,
> + board->sensors[oxp_sensor_pwm].val_enable);
> +
> + return ret;
... and unnecessary variable.
> +}
> +
> +static int oxp_pwm_disable(const struct device *dev)
> +{
> + int ret = -1;
Unnecessary initialization, and bad error code.
> + struct oxp_status *state = dev_get_drvdata(dev);
> + const struct ec_board_info *board = &state->board;
> +
> + if (!fan_control) {
> + dev_info(dev, "Can't disable pwm, fan_control=0");
> + return -EPERM;
> + }
I really don't see the point of the "fan_control" module parameter.
One can set it to 1 (or true) to enable the pwm_enable attribute,
or set it to 0 to disable it. It is effectively the same as just
another attribute, forcing users to write two attributes instead
of one. That really doesn't make sense.
> +
> + ret = write_to_ec(dev, board->sensors[oxp_sensor_pwm].enable,
> + board->sensors[oxp_sensor_pwm].val_disable);
> +
> + return ret;
Just
return write_to_ec(...);
would do. There is no need for the ret variable. Same elsewhere.
> +}
> +
> +static int oxp_platform_write(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long val)
> +{
> + int ret = -1;
bad error code.
> + struct oxp_status *state = dev_get_drvdata(dev);
> + const struct ec_board_info *board = &state->board;
> +
> + switch(type) {
> + case hwmon_pwm:
> + if (!fan_control) {
> + dev_info(dev, "Can't control fans, fan_control=0");
> + return -EPERM;
> + }
> + switch(attr) {
> + case hwmon_pwm_enable:
> + if (val == 1) {
> + ret = oxp_pwm_enable(dev);
> + } else if (val == 0) {
> + ret = oxp_pwm_disable(dev);
> + } else {
> + return -EINVAL;
> + }
Unnecessary { }, and the single return on error instead of
ret = -EINVAL;
is inconsistent.
> + return ret;
> + case hwmon_pwm_input:
> + if (val < 0 || val > 255)
> + return -EINVAL;
> + if (board->family == family_mini_amd)
> + val = (val * 100) / 255;
> + ret = write_to_ec(dev, board->sensors[oxp_sensor_pwm].reg, val);
> + return ret;
> + default:
> + dev_dbg(dev, "Unknown attribute for type %d: %d", type, attr);
> + return ret;
> + }
> + default:
> + dev_dbg(dev, "Unknown sensor type: %d", type);
break missing
> + }
> + return ret;
> +}
> +
> +/* Known sensors in the OXP EC controllers */
> +static const struct hwmon_channel_info *oxp_platform_sensors[] = {
> + HWMON_CHANNEL_INFO(fan,
> + HWMON_F_INPUT | HWMON_F_MAX | HWMON_F_MIN),
> + HWMON_CHANNEL_INFO(pwm,
> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
bad alignment. Please use checkpatch --strict and fix what it reports.
> + NULL
> +};
> +
> +static const struct hwmon_ops oxp_ec_hwmon_ops = {
> + .is_visible = oxp_ec_hwmon_is_visible,
> + .read = oxp_platform_read,
> + .write = oxp_platform_write,
> +};
> +
> +static const struct hwmon_chip_info oxp_ec_chip_info = {
> + .ops = &oxp_ec_hwmon_ops,
> + .info = oxp_platform_sensors,
> +};
> +
> +/* Initialization logic */
> +static const struct ec_board_info * __init get_board_info(struct device *dev)
> +{
> + const char *dmi_board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR);
> + const char *dmi_board_name = dmi_get_system_info(DMI_BOARD_NAME);
> + const struct ec_board_info *board;
> +
> + if (!dmi_board_vendor || !dmi_board_name ||
> + (strcasecmp(dmi_board_vendor, "ONE-NETBOOK TECHNOLOGY CO., LTD.") &&
> + strcasecmp(dmi_board_vendor, "ONE-NETBOOK")))
> + return NULL;
> +
> + /* Match our known boards */
> + /* Need to check for AMD/Intel here */
> + for (board = board_info; board->sensors; board++) {
> + if (match_string(board->board_names,
> + MAX_IDENTICAL_BOARD_VARIATIONS,
> + dmi_board_name) >= 0) {
> + if (board->family == family_mini_amd &&
> + boot_cpu_data.x86_vendor == X86_VENDOR_AMD) {
> + return board;
> + }
> + }
> + }
> + return NULL;
Why not use a dmi match table for the above code ?
> +}
> +
> +static int __init oxp_platform_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct device *hwdev;
> + const struct ec_board_info *pboard_info;
> + struct oxp_status *state;
> +
> + pboard_info = get_board_info(dev);
> + if (!pboard_info)
> + return -ENODEV;
> +
> + state = devm_kzalloc(dev, sizeof(struct oxp_status), GFP_KERNEL);
> + if (!state)
> + return -ENOMEM;
> +
> + state->board = *pboard_info;
> +
> + state->lock_data.mutex = 0;
> + state->lock_data.lock = lock_global_acpi_lock;
> + state->lock_data.unlock = unlock_global_acpi_lock;
> +
> + hwdev = devm_hwmon_device_register_with_info(dev, "oxpec", state,
> + &oxp_ec_chip_info, NULL);
> +
> + return PTR_ERR_OR_ZERO(hwdev);
This only configures a hwmon driver and thus should reside in the hwmon
subsystem/directory.
> +}
> +
> +static const struct acpi_device_id acpi_ec_ids[] = {
> + /* Embedded Controller Device */
> + { "PNP0C09", 0 },
> + {}
> +};
I am not sure if this works. There are other drivers mapping to the same ACPI ID;
those may refuse to load if this driver is in the system. We had problems with
this before, so I am very concerned about side effects.
> +
> +static struct platform_driver oxp_platform_driver = {
> + .driver = {
> + .name = "oxp-platform",
> + .acpi_match_table = acpi_ec_ids,
> + },
> +};
> +
> +MODULE_DEVICE_TABLE(acpi, acpi_ec_ids);
> +module_platform_driver_probe(oxp_platform_driver, oxp_platform_probe);
> +
> +MODULE_AUTHOR("Joaquín Ignacio Aramendía <samsagax@gmail.com>");
> +MODULE_DESCRIPTION(
> + "Platform driver that handles ACPI EC of OneXPlayer devices");
> +MODULE_LICENSE("GPL");
> --
> 2.38.1
>
next prev parent reply other threads:[~2022-10-29 23:31 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-10-29 22:50 [PATCH] Add OneXPlayer mini AMD board driver Joaquín Ignacio Aramendía
2022-10-29 23:30 ` Guenter Roeck [this message]
2022-10-30 0:29 ` Joaquin Aramendia
2022-10-30 3:24 ` Guenter Roeck
2022-10-30 20:32 ` [PATCH v2] Add OneXPlayer mini AMD sensors driver Joaquín Ignacio Aramendía
2022-10-31 0:03 ` Barnabás Pőcze
2022-10-31 14:49 ` Joaquin Aramendia
2022-10-31 14:53 ` [PATCH v3] " Joaquín Ignacio Aramendía
2022-10-31 15:34 ` Guenter Roeck
2022-10-31 15:57 ` Joaquin Aramendia
2022-10-31 16:43 ` Limonciello, Mario
2022-10-31 18:57 ` Joaquin Aramendia
2022-10-31 19:21 ` Limonciello, Mario
2022-10-31 19:33 ` Joaquin Aramendia
2022-10-31 19:56 ` Guenter Roeck
2022-10-31 20:53 ` Joaquin Aramendia
2022-10-31 21:30 ` Guenter Roeck
2022-10-31 11:45 ` [PATCH] Add OneXPlayer mini AMD board driver Joaquin Aramendia
2022-10-31 13:07 ` Guenter Roeck
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=506a6e7f-4566-2dcf-37f3-0f41f4ce983b@roeck-us.net \
--to=linux@roeck-us.net \
--cc=hdegoede@redhat.com \
--cc=jdelvare@suse.com \
--cc=linux-hwmon@vger.kernel.org \
--cc=markgross@kernel.org \
--cc=platform-driver-x86@vger.kernel.org \
--cc=samsagax@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).