Merge tag 'v5.3-rc4' into next
Sync up with mainline to bring in device_property_count_u32 andother newer APIs.
Esse commit está contido em:
@@ -11,3 +11,5 @@ source "drivers/platform/goldfish/Kconfig"
|
||||
source "drivers/platform/chrome/Kconfig"
|
||||
|
||||
source "drivers/platform/mellanox/Kconfig"
|
||||
|
||||
source "drivers/platform/olpc/Kconfig"
|
||||
|
@@ -6,6 +6,6 @@
|
||||
obj-$(CONFIG_X86) += x86/
|
||||
obj-$(CONFIG_MELLANOX_PLATFORM) += mellanox/
|
||||
obj-$(CONFIG_MIPS) += mips/
|
||||
obj-$(CONFIG_OLPC) += olpc/
|
||||
obj-$(CONFIG_OLPC_EC) += olpc/
|
||||
obj-$(CONFIG_GOLDFISH) += goldfish/
|
||||
obj-$(CONFIG_CHROME_PLATFORMS) += chrome/
|
||||
|
@@ -72,6 +72,19 @@ config CROS_EC_RPMSG
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called cros_ec_rpmsg.
|
||||
|
||||
config CROS_EC_ISHTP
|
||||
tristate "ChromeOS Embedded Controller (ISHTP)"
|
||||
depends on MFD_CROS_EC
|
||||
depends on INTEL_ISH_HID
|
||||
help
|
||||
If you say Y here, you get support for talking to the ChromeOS EC
|
||||
firmware running on Intel Integrated Sensor Hub (ISH), using the
|
||||
ISH Transport protocol (ISH-TP). This uses a simple byte-level
|
||||
protocol with a checksum.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called cros_ec_ishtp.
|
||||
|
||||
config CROS_EC_SPI
|
||||
tristate "ChromeOS Embedded Controller (SPI)"
|
||||
depends on MFD_CROS_EC && SPI
|
||||
@@ -83,28 +96,17 @@ config CROS_EC_SPI
|
||||
'pre-amble' bytes before the response actually starts.
|
||||
|
||||
config CROS_EC_LPC
|
||||
tristate "ChromeOS Embedded Controller (LPC)"
|
||||
depends on MFD_CROS_EC && ACPI && (X86 || COMPILE_TEST)
|
||||
help
|
||||
If you say Y here, you get support for talking to the ChromeOS EC
|
||||
over an LPC bus. This uses a simple byte-level protocol with a
|
||||
checksum. This is used for userspace access only. The kernel
|
||||
typically has its own communication methods.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called cros_ec_lpc.
|
||||
|
||||
config CROS_EC_LPC_MEC
|
||||
bool "ChromeOS Embedded Controller LPC Microchip EC (MEC) variant"
|
||||
depends on CROS_EC_LPC
|
||||
default n
|
||||
tristate "ChromeOS Embedded Controller (LPC)"
|
||||
depends on MFD_CROS_EC && ACPI && (X86 || COMPILE_TEST)
|
||||
help
|
||||
If you say Y here, a variant LPC protocol for the Microchip EC
|
||||
will be used. Note that this variant is not backward compatible
|
||||
with non-Microchip ECs.
|
||||
If you say Y here, you get support for talking to the ChromeOS EC
|
||||
over an LPC bus, including the LPC Microchip EC (MEC) variant.
|
||||
This uses a simple byte-level protocol with a checksum. This is
|
||||
used for userspace access only. The kernel typically has its own
|
||||
communication methods.
|
||||
|
||||
If you have a ChromeOS Embedded Controller Microchip EC variant
|
||||
choose Y here.
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called cros_ec_lpcs.
|
||||
|
||||
config CROS_EC_PROTO
|
||||
bool
|
||||
|
@@ -7,10 +7,10 @@ obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o
|
||||
obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o
|
||||
obj-$(CONFIG_CHROMEOS_TBMC) += chromeos_tbmc.o
|
||||
obj-$(CONFIG_CROS_EC_I2C) += cros_ec_i2c.o
|
||||
obj-$(CONFIG_CROS_EC_ISHTP) += cros_ec_ishtp.o
|
||||
obj-$(CONFIG_CROS_EC_RPMSG) += cros_ec_rpmsg.o
|
||||
obj-$(CONFIG_CROS_EC_SPI) += cros_ec_spi.o
|
||||
cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_reg.o
|
||||
cros_ec_lpcs-$(CONFIG_CROS_EC_LPC_MEC) += cros_ec_lpc_mec.o
|
||||
cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_mec.o
|
||||
obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpcs.o
|
||||
obj-$(CONFIG_CROS_EC_PROTO) += cros_ec_proto.o cros_ec_trace.o
|
||||
obj-$(CONFIG_CROS_KBD_LED_BACKLIGHT) += cros_kbd_led_backlight.o
|
||||
|
@@ -25,7 +25,8 @@
|
||||
|
||||
#define CIRC_ADD(idx, size, value) (((idx) + (value)) & ((size) - 1))
|
||||
|
||||
/* struct cros_ec_debugfs - ChromeOS EC debugging information
|
||||
/**
|
||||
* struct cros_ec_debugfs - EC debugging information.
|
||||
*
|
||||
* @ec: EC device this debugfs information belongs to
|
||||
* @dir: dentry for debugfs files
|
||||
@@ -241,7 +242,35 @@ static ssize_t cros_ec_pdinfo_read(struct file *file,
|
||||
read_buf, p - read_buf);
|
||||
}
|
||||
|
||||
const struct file_operations cros_ec_console_log_fops = {
|
||||
static ssize_t cros_ec_uptime_read(struct file *file, char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct cros_ec_debugfs *debug_info = file->private_data;
|
||||
struct cros_ec_device *ec_dev = debug_info->ec->ec_dev;
|
||||
struct {
|
||||
struct cros_ec_command cmd;
|
||||
struct ec_response_uptime_info resp;
|
||||
} __packed msg = {};
|
||||
struct ec_response_uptime_info *resp;
|
||||
char read_buf[32];
|
||||
int ret;
|
||||
|
||||
resp = (struct ec_response_uptime_info *)&msg.resp;
|
||||
|
||||
msg.cmd.command = EC_CMD_GET_UPTIME_INFO;
|
||||
msg.cmd.insize = sizeof(*resp);
|
||||
|
||||
ret = cros_ec_cmd_xfer_status(ec_dev, &msg.cmd);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = scnprintf(read_buf, sizeof(read_buf), "%u\n",
|
||||
resp->time_since_ec_boot_ms);
|
||||
|
||||
return simple_read_from_buffer(user_buf, count, ppos, read_buf, ret);
|
||||
}
|
||||
|
||||
static const struct file_operations cros_ec_console_log_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = cros_ec_console_log_open,
|
||||
.read = cros_ec_console_log_read,
|
||||
@@ -250,13 +279,20 @@ const struct file_operations cros_ec_console_log_fops = {
|
||||
.release = cros_ec_console_log_release,
|
||||
};
|
||||
|
||||
const struct file_operations cros_ec_pdinfo_fops = {
|
||||
static const struct file_operations cros_ec_pdinfo_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = simple_open,
|
||||
.read = cros_ec_pdinfo_read,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static const struct file_operations cros_ec_uptime_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = simple_open,
|
||||
.read = cros_ec_uptime_read,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static int ec_read_version_supported(struct cros_ec_dev *ec)
|
||||
{
|
||||
struct ec_params_get_cmd_versions_v1 *params;
|
||||
@@ -408,6 +444,12 @@ static int cros_ec_debugfs_probe(struct platform_device *pd)
|
||||
debugfs_create_file("pdinfo", 0444, debug_info->dir, debug_info,
|
||||
&cros_ec_pdinfo_fops);
|
||||
|
||||
debugfs_create_file("uptime", 0444, debug_info->dir, debug_info,
|
||||
&cros_ec_uptime_fops);
|
||||
|
||||
debugfs_create_x32("last_resume_result", 0444, debug_info->dir,
|
||||
&ec->ec_dev->last_resume_result);
|
||||
|
||||
ec->debug_info = debug_info;
|
||||
|
||||
dev_set_drvdata(&pd->dev, ec);
|
||||
|
763
drivers/platform/chrome/cros_ec_ishtp.c
Arquivo normal
763
drivers/platform/chrome/cros_ec_ishtp.c
Arquivo normal
@@ -0,0 +1,763 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// ISHTP interface for ChromeOS Embedded Controller
|
||||
//
|
||||
// Copyright (c) 2019, Intel Corporation.
|
||||
//
|
||||
// ISHTP client driver for talking to the Chrome OS EC firmware running
|
||||
// on Intel Integrated Sensor Hub (ISH) using the ISH Transport protocol
|
||||
// (ISH-TP).
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/cros_ec.h>
|
||||
#include <linux/mfd/cros_ec_commands.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/intel-ish-client-if.h>
|
||||
|
||||
/*
|
||||
* ISH TX/RX ring buffer pool size
|
||||
*
|
||||
* The AP->ISH messages and corresponding ISH->AP responses are
|
||||
* serialized. We need 1 TX and 1 RX buffer for these.
|
||||
*
|
||||
* The MKBP ISH->AP events are serialized. We need one additional RX
|
||||
* buffer for them.
|
||||
*/
|
||||
#define CROS_ISH_CL_TX_RING_SIZE 8
|
||||
#define CROS_ISH_CL_RX_RING_SIZE 8
|
||||
|
||||
/* ISH CrOS EC Host Commands */
|
||||
enum cros_ec_ish_channel {
|
||||
CROS_EC_COMMAND = 1, /* AP->ISH message */
|
||||
CROS_MKBP_EVENT = 2, /* ISH->AP events */
|
||||
};
|
||||
|
||||
/*
|
||||
* ISH firmware timeout for 1 message send failure is 1Hz, and the
|
||||
* firmware will retry 2 times, so 3Hz is used for timeout.
|
||||
*/
|
||||
#define ISHTP_SEND_TIMEOUT (3 * HZ)
|
||||
|
||||
/* ISH Transport CrOS EC ISH client unique GUID */
|
||||
static const guid_t cros_ish_guid =
|
||||
GUID_INIT(0x7b7154d0, 0x56f4, 0x4bdc,
|
||||
0xb0, 0xd8, 0x9e, 0x7c, 0xda, 0xe0, 0xd6, 0xa0);
|
||||
|
||||
struct header {
|
||||
u8 channel;
|
||||
u8 status;
|
||||
u8 reserved[2];
|
||||
} __packed;
|
||||
|
||||
struct cros_ish_out_msg {
|
||||
struct header hdr;
|
||||
struct ec_host_request ec_request;
|
||||
} __packed;
|
||||
|
||||
struct cros_ish_in_msg {
|
||||
struct header hdr;
|
||||
struct ec_host_response ec_response;
|
||||
} __packed;
|
||||
|
||||
#define IN_MSG_EC_RESPONSE_PREAMBLE \
|
||||
offsetof(struct cros_ish_in_msg, ec_response)
|
||||
|
||||
#define OUT_MSG_EC_REQUEST_PREAMBLE \
|
||||
offsetof(struct cros_ish_out_msg, ec_request)
|
||||
|
||||
#define cl_data_to_dev(client_data) ishtp_device((client_data)->cl_device)
|
||||
|
||||
/*
|
||||
* The Read-Write Semaphore is used to prevent message TX or RX while
|
||||
* the ishtp client is being initialized or undergoing reset.
|
||||
*
|
||||
* The readers are the kernel function calls responsible for IA->ISH
|
||||
* and ISH->AP messaging.
|
||||
*
|
||||
* The writers are .reset() and .probe() function.
|
||||
*/
|
||||
DECLARE_RWSEM(init_lock);
|
||||
|
||||
/**
|
||||
* struct response_info - Encapsulate firmware response related
|
||||
* information for passing between function ish_send() and
|
||||
* process_recv() callback.
|
||||
*
|
||||
* @data: Copy the data received from firmware here.
|
||||
* @max_size: Max size allocated for the @data buffer. If the received
|
||||
* data exceeds this value, we log an error.
|
||||
* @size: Actual size of data received from firmware.
|
||||
* @error: 0 for success, negative error code for a failure in process_recv().
|
||||
* @received: Set to true on receiving a valid firmware response to host command
|
||||
* @wait_queue: Wait queue for host to wait for firmware response.
|
||||
*/
|
||||
struct response_info {
|
||||
void *data;
|
||||
size_t max_size;
|
||||
size_t size;
|
||||
int error;
|
||||
bool received;
|
||||
wait_queue_head_t wait_queue;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ishtp_cl_data - Encapsulate per ISH TP Client.
|
||||
*
|
||||
* @cros_ish_cl: ISHTP firmware client instance.
|
||||
* @cl_device: ISHTP client device instance.
|
||||
* @response: Response info passing between ish_send() and process_recv().
|
||||
* @work_ishtp_reset: Work queue reset handling.
|
||||
* @work_ec_evt: Work queue for EC events.
|
||||
* @ec_dev: CrOS EC MFD device.
|
||||
*
|
||||
* This structure is used to store per client data.
|
||||
*/
|
||||
struct ishtp_cl_data {
|
||||
struct ishtp_cl *cros_ish_cl;
|
||||
struct ishtp_cl_device *cl_device;
|
||||
|
||||
/*
|
||||
* Used for passing firmware response information between
|
||||
* ish_send() and process_recv() callback.
|
||||
*/
|
||||
struct response_info response;
|
||||
|
||||
struct work_struct work_ishtp_reset;
|
||||
struct work_struct work_ec_evt;
|
||||
struct cros_ec_device *ec_dev;
|
||||
};
|
||||
|
||||
/**
|
||||
* ish_evt_handler - ISH to AP event handler
|
||||
* @work: Work struct
|
||||
*/
|
||||
static void ish_evt_handler(struct work_struct *work)
|
||||
{
|
||||
struct ishtp_cl_data *client_data =
|
||||
container_of(work, struct ishtp_cl_data, work_ec_evt);
|
||||
struct cros_ec_device *ec_dev = client_data->ec_dev;
|
||||
|
||||
if (cros_ec_get_next_event(ec_dev, NULL) > 0) {
|
||||
blocking_notifier_call_chain(&ec_dev->event_notifier,
|
||||
0, ec_dev);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ish_send() - Send message from host to firmware
|
||||
*
|
||||
* @client_data: Client data instance
|
||||
* @out_msg: Message buffer to be sent to firmware
|
||||
* @out_size: Size of out going message
|
||||
* @in_msg: Message buffer where the incoming data is copied. This buffer
|
||||
* is allocated by calling
|
||||
* @in_size: Max size of incoming message
|
||||
*
|
||||
* Return: Number of bytes copied in the in_msg on success, negative
|
||||
* error code on failure.
|
||||
*/
|
||||
static int ish_send(struct ishtp_cl_data *client_data,
|
||||
u8 *out_msg, size_t out_size,
|
||||
u8 *in_msg, size_t in_size)
|
||||
{
|
||||
int rv;
|
||||
struct header *out_hdr = (struct header *)out_msg;
|
||||
struct ishtp_cl *cros_ish_cl = client_data->cros_ish_cl;
|
||||
|
||||
dev_dbg(cl_data_to_dev(client_data),
|
||||
"%s: channel=%02u status=%02u\n",
|
||||
__func__, out_hdr->channel, out_hdr->status);
|
||||
|
||||
/* Setup for incoming response */
|
||||
client_data->response.data = in_msg;
|
||||
client_data->response.max_size = in_size;
|
||||
client_data->response.error = 0;
|
||||
client_data->response.received = false;
|
||||
|
||||
rv = ishtp_cl_send(cros_ish_cl, out_msg, out_size);
|
||||
if (rv) {
|
||||
dev_err(cl_data_to_dev(client_data),
|
||||
"ishtp_cl_send error %d\n", rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
wait_event_interruptible_timeout(client_data->response.wait_queue,
|
||||
client_data->response.received,
|
||||
ISHTP_SEND_TIMEOUT);
|
||||
if (!client_data->response.received) {
|
||||
dev_err(cl_data_to_dev(client_data),
|
||||
"Timed out for response to host message\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
if (client_data->response.error < 0)
|
||||
return client_data->response.error;
|
||||
|
||||
return client_data->response.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* process_recv() - Received and parse incoming packet
|
||||
* @cros_ish_cl: Client instance to get stats
|
||||
* @rb_in_proc: Host interface message buffer
|
||||
*
|
||||
* Parse the incoming packet. If it is a response packet then it will
|
||||
* update per instance flags and wake up the caller waiting to for the
|
||||
* response. If it is an event packet then it will schedule event work.
|
||||
*/
|
||||
static void process_recv(struct ishtp_cl *cros_ish_cl,
|
||||
struct ishtp_cl_rb *rb_in_proc)
|
||||
{
|
||||
size_t data_len = rb_in_proc->buf_idx;
|
||||
struct ishtp_cl_data *client_data =
|
||||
ishtp_get_client_data(cros_ish_cl);
|
||||
struct device *dev = cl_data_to_dev(client_data);
|
||||
struct cros_ish_in_msg *in_msg =
|
||||
(struct cros_ish_in_msg *)rb_in_proc->buffer.data;
|
||||
|
||||
/* Proceed only if reset or init is not in progress */
|
||||
if (!down_read_trylock(&init_lock)) {
|
||||
/* Free the buffer */
|
||||
ishtp_cl_io_rb_recycle(rb_in_proc);
|
||||
dev_warn(dev,
|
||||
"Host is not ready to receive incoming messages\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* All firmware messages contain a header. Check the buffer size
|
||||
* before accessing elements inside.
|
||||
*/
|
||||
if (!rb_in_proc->buffer.data) {
|
||||
dev_warn(dev, "rb_in_proc->buffer.data returned null");
|
||||
client_data->response.error = -EBADMSG;
|
||||
goto end_error;
|
||||
}
|
||||
|
||||
if (data_len < sizeof(struct header)) {
|
||||
dev_err(dev, "data size %zu is less than header %zu\n",
|
||||
data_len, sizeof(struct header));
|
||||
client_data->response.error = -EMSGSIZE;
|
||||
goto end_error;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "channel=%02u status=%02u\n",
|
||||
in_msg->hdr.channel, in_msg->hdr.status);
|
||||
|
||||
switch (in_msg->hdr.channel) {
|
||||
case CROS_EC_COMMAND:
|
||||
/* Sanity check */
|
||||
if (!client_data->response.data) {
|
||||
dev_err(dev,
|
||||
"Receiving buffer is null. Should be allocated by calling function\n");
|
||||
client_data->response.error = -EINVAL;
|
||||
goto error_wake_up;
|
||||
}
|
||||
|
||||
if (client_data->response.received) {
|
||||
dev_err(dev,
|
||||
"Previous firmware message not yet processed\n");
|
||||
client_data->response.error = -EINVAL;
|
||||
goto error_wake_up;
|
||||
}
|
||||
|
||||
if (data_len > client_data->response.max_size) {
|
||||
dev_err(dev,
|
||||
"Received buffer size %zu is larger than allocated buffer %zu\n",
|
||||
data_len, client_data->response.max_size);
|
||||
client_data->response.error = -EMSGSIZE;
|
||||
goto error_wake_up;
|
||||
}
|
||||
|
||||
if (in_msg->hdr.status) {
|
||||
dev_err(dev, "firmware returned status %d\n",
|
||||
in_msg->hdr.status);
|
||||
client_data->response.error = -EIO;
|
||||
goto error_wake_up;
|
||||
}
|
||||
|
||||
/* Update the actual received buffer size */
|
||||
client_data->response.size = data_len;
|
||||
|
||||
/*
|
||||
* Copy the buffer received in firmware response for the
|
||||
* calling thread.
|
||||
*/
|
||||
memcpy(client_data->response.data,
|
||||
rb_in_proc->buffer.data, data_len);
|
||||
|
||||
/* Set flag before waking up the caller */
|
||||
client_data->response.received = true;
|
||||
error_wake_up:
|
||||
/* Wake the calling thread */
|
||||
wake_up_interruptible(&client_data->response.wait_queue);
|
||||
|
||||
break;
|
||||
|
||||
case CROS_MKBP_EVENT:
|
||||
/* The event system doesn't send any data in buffer */
|
||||
schedule_work(&client_data->work_ec_evt);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_err(dev, "Invalid channel=%02d\n", in_msg->hdr.channel);
|
||||
}
|
||||
|
||||
end_error:
|
||||
/* Free the buffer */
|
||||
ishtp_cl_io_rb_recycle(rb_in_proc);
|
||||
|
||||
up_read(&init_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* ish_event_cb() - bus driver callback for incoming message
|
||||
* @cl_device: ISHTP client device for which this message is targeted.
|
||||
*
|
||||
* Remove the packet from the list and process the message by calling
|
||||
* process_recv.
|
||||
*/
|
||||
static void ish_event_cb(struct ishtp_cl_device *cl_device)
|
||||
{
|
||||
struct ishtp_cl_rb *rb_in_proc;
|
||||
struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
|
||||
|
||||
while ((rb_in_proc = ishtp_cl_rx_get_rb(cros_ish_cl)) != NULL) {
|
||||
/* Decide what to do with received data */
|
||||
process_recv(cros_ish_cl, rb_in_proc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* cros_ish_init() - Init function for ISHTP client
|
||||
* @cros_ish_cl: ISHTP client instance
|
||||
*
|
||||
* This function complete the initializtion of the client.
|
||||
*
|
||||
* Return: 0 for success, negative error code for failure.
|
||||
*/
|
||||
static int cros_ish_init(struct ishtp_cl *cros_ish_cl)
|
||||
{
|
||||
int rv;
|
||||
struct ishtp_device *dev;
|
||||
struct ishtp_fw_client *fw_client;
|
||||
struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
|
||||
|
||||
rv = ishtp_cl_link(cros_ish_cl);
|
||||
if (rv) {
|
||||
dev_err(cl_data_to_dev(client_data),
|
||||
"ishtp_cl_link failed\n");
|
||||
return rv;
|
||||
}
|
||||
|
||||
dev = ishtp_get_ishtp_device(cros_ish_cl);
|
||||
|
||||
/* Connect to firmware client */
|
||||
ishtp_set_tx_ring_size(cros_ish_cl, CROS_ISH_CL_TX_RING_SIZE);
|
||||
ishtp_set_rx_ring_size(cros_ish_cl, CROS_ISH_CL_RX_RING_SIZE);
|
||||
|
||||
fw_client = ishtp_fw_cl_get_client(dev, &cros_ish_guid);
|
||||
if (!fw_client) {
|
||||
dev_err(cl_data_to_dev(client_data),
|
||||
"ish client uuid not found\n");
|
||||
rv = -ENOENT;
|
||||
goto err_cl_unlink;
|
||||
}
|
||||
|
||||
ishtp_cl_set_fw_client_id(cros_ish_cl,
|
||||
ishtp_get_fw_client_id(fw_client));
|
||||
ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_CONNECTING);
|
||||
|
||||
rv = ishtp_cl_connect(cros_ish_cl);
|
||||
if (rv) {
|
||||
dev_err(cl_data_to_dev(client_data),
|
||||
"client connect fail\n");
|
||||
goto err_cl_unlink;
|
||||
}
|
||||
|
||||
ishtp_register_event_cb(client_data->cl_device, ish_event_cb);
|
||||
return 0;
|
||||
|
||||
err_cl_unlink:
|
||||
ishtp_cl_unlink(cros_ish_cl);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* cros_ish_deinit() - Deinit function for ISHTP client
|
||||
* @cros_ish_cl: ISHTP client instance
|
||||
*
|
||||
* Unlink and free cros_ec client
|
||||
*/
|
||||
static void cros_ish_deinit(struct ishtp_cl *cros_ish_cl)
|
||||
{
|
||||
ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_DISCONNECTING);
|
||||
ishtp_cl_disconnect(cros_ish_cl);
|
||||
ishtp_cl_unlink(cros_ish_cl);
|
||||
ishtp_cl_flush_queues(cros_ish_cl);
|
||||
|
||||
/* Disband and free all Tx and Rx client-level rings */
|
||||
ishtp_cl_free(cros_ish_cl);
|
||||
}
|
||||
|
||||
/**
|
||||
* prepare_cros_ec_rx() - Check & prepare receive buffer
|
||||
* @ec_dev: CrOS EC MFD device.
|
||||
* @in_msg: Incoming message buffer
|
||||
* @msg: cros_ec command used to send & receive data
|
||||
*
|
||||
* Return: 0 for success, negative error code for failure.
|
||||
*
|
||||
* Check the received buffer. Convert to cros_ec_command format.
|
||||
*/
|
||||
static int prepare_cros_ec_rx(struct cros_ec_device *ec_dev,
|
||||
const struct cros_ish_in_msg *in_msg,
|
||||
struct cros_ec_command *msg)
|
||||
{
|
||||
u8 sum = 0;
|
||||
int i, rv, offset;
|
||||
|
||||
/* Check response error code */
|
||||
msg->result = in_msg->ec_response.result;
|
||||
rv = cros_ec_check_result(ec_dev, msg);
|
||||
if (rv < 0)
|
||||
return rv;
|
||||
|
||||
if (in_msg->ec_response.data_len > msg->insize) {
|
||||
dev_err(ec_dev->dev, "Packet too long (%d bytes, expected %d)",
|
||||
in_msg->ec_response.data_len, msg->insize);
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
/* Copy response packet payload and compute checksum */
|
||||
for (i = 0; i < sizeof(struct ec_host_response); i++)
|
||||
sum += ((u8 *)in_msg)[IN_MSG_EC_RESPONSE_PREAMBLE + i];
|
||||
|
||||
offset = sizeof(struct cros_ish_in_msg);
|
||||
for (i = 0; i < in_msg->ec_response.data_len; i++)
|
||||
sum += msg->data[i] = ((u8 *)in_msg)[offset + i];
|
||||
|
||||
if (sum) {
|
||||
dev_dbg(ec_dev->dev, "Bad received packet checksum %d\n", sum);
|
||||
return -EBADMSG;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cros_ec_pkt_xfer_ish(struct cros_ec_device *ec_dev,
|
||||
struct cros_ec_command *msg)
|
||||
{
|
||||
int rv;
|
||||
struct ishtp_cl *cros_ish_cl = ec_dev->priv;
|
||||
struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
|
||||
struct device *dev = cl_data_to_dev(client_data);
|
||||
struct cros_ish_in_msg *in_msg = (struct cros_ish_in_msg *)ec_dev->din;
|
||||
struct cros_ish_out_msg *out_msg =
|
||||
(struct cros_ish_out_msg *)ec_dev->dout;
|
||||
size_t in_size = sizeof(struct cros_ish_in_msg) + msg->insize;
|
||||
size_t out_size = sizeof(struct cros_ish_out_msg) + msg->outsize;
|
||||
|
||||
/* Sanity checks */
|
||||
if (in_size > ec_dev->din_size) {
|
||||
dev_err(dev,
|
||||
"Incoming payload size %zu is too large for ec_dev->din_size %d\n",
|
||||
in_size, ec_dev->din_size);
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
if (out_size > ec_dev->dout_size) {
|
||||
dev_err(dev,
|
||||
"Outgoing payload size %zu is too large for ec_dev->dout_size %d\n",
|
||||
out_size, ec_dev->dout_size);
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
/* Proceed only if reset-init is not in progress */
|
||||
if (!down_read_trylock(&init_lock)) {
|
||||
dev_warn(dev,
|
||||
"Host is not ready to send messages to ISH. Try again\n");
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
/* Prepare the package to be sent over ISH TP */
|
||||
out_msg->hdr.channel = CROS_EC_COMMAND;
|
||||
out_msg->hdr.status = 0;
|
||||
|
||||
ec_dev->dout += OUT_MSG_EC_REQUEST_PREAMBLE;
|
||||
cros_ec_prepare_tx(ec_dev, msg);
|
||||
ec_dev->dout -= OUT_MSG_EC_REQUEST_PREAMBLE;
|
||||
|
||||
dev_dbg(dev,
|
||||
"out_msg: struct_ver=0x%x checksum=0x%x command=0x%x command_ver=0x%x data_len=0x%x\n",
|
||||
out_msg->ec_request.struct_version,
|
||||
out_msg->ec_request.checksum,
|
||||
out_msg->ec_request.command,
|
||||
out_msg->ec_request.command_version,
|
||||
out_msg->ec_request.data_len);
|
||||
|
||||
/* Send command to ISH EC firmware and read response */
|
||||
rv = ish_send(client_data,
|
||||
(u8 *)out_msg, out_size,
|
||||
(u8 *)in_msg, in_size);
|
||||
if (rv < 0)
|
||||
goto end_error;
|
||||
|
||||
rv = prepare_cros_ec_rx(ec_dev, in_msg, msg);
|
||||
if (rv)
|
||||
goto end_error;
|
||||
|
||||
rv = in_msg->ec_response.data_len;
|
||||
|
||||
dev_dbg(dev,
|
||||
"in_msg: struct_ver=0x%x checksum=0x%x result=0x%x data_len=0x%x\n",
|
||||
in_msg->ec_response.struct_version,
|
||||
in_msg->ec_response.checksum,
|
||||
in_msg->ec_response.result,
|
||||
in_msg->ec_response.data_len);
|
||||
|
||||
end_error:
|
||||
if (msg->command == EC_CMD_REBOOT_EC)
|
||||
msleep(EC_REBOOT_DELAY_MS);
|
||||
|
||||
up_read(&init_lock);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int cros_ec_dev_init(struct ishtp_cl_data *client_data)
|
||||
{
|
||||
struct cros_ec_device *ec_dev;
|
||||
struct device *dev = cl_data_to_dev(client_data);
|
||||
|
||||
ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL);
|
||||
if (!ec_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
client_data->ec_dev = ec_dev;
|
||||
dev->driver_data = ec_dev;
|
||||
|
||||
ec_dev->dev = dev;
|
||||
ec_dev->priv = client_data->cros_ish_cl;
|
||||
ec_dev->cmd_xfer = NULL;
|
||||
ec_dev->pkt_xfer = cros_ec_pkt_xfer_ish;
|
||||
ec_dev->phys_name = dev_name(dev);
|
||||
ec_dev->din_size = sizeof(struct cros_ish_in_msg) +
|
||||
sizeof(struct ec_response_get_protocol_info);
|
||||
ec_dev->dout_size = sizeof(struct cros_ish_out_msg);
|
||||
|
||||
return cros_ec_register(ec_dev);
|
||||
}
|
||||
|
||||
static void reset_handler(struct work_struct *work)
|
||||
{
|
||||
int rv;
|
||||
struct device *dev;
|
||||
struct ishtp_cl *cros_ish_cl;
|
||||
struct ishtp_cl_device *cl_device;
|
||||
struct ishtp_cl_data *client_data =
|
||||
container_of(work, struct ishtp_cl_data, work_ishtp_reset);
|
||||
|
||||
/* Lock for reset to complete */
|
||||
down_write(&init_lock);
|
||||
|
||||
cros_ish_cl = client_data->cros_ish_cl;
|
||||
cl_device = client_data->cl_device;
|
||||
|
||||
/* Unlink, flush queues & start again */
|
||||
ishtp_cl_unlink(cros_ish_cl);
|
||||
ishtp_cl_flush_queues(cros_ish_cl);
|
||||
ishtp_cl_free(cros_ish_cl);
|
||||
|
||||
cros_ish_cl = ishtp_cl_allocate(cl_device);
|
||||
if (!cros_ish_cl) {
|
||||
up_write(&init_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
ishtp_set_drvdata(cl_device, cros_ish_cl);
|
||||
ishtp_set_client_data(cros_ish_cl, client_data);
|
||||
client_data->cros_ish_cl = cros_ish_cl;
|
||||
|
||||
rv = cros_ish_init(cros_ish_cl);
|
||||
if (rv) {
|
||||
ishtp_cl_free(cros_ish_cl);
|
||||
dev_err(cl_data_to_dev(client_data), "Reset Failed\n");
|
||||
up_write(&init_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Refresh ec_dev device pointers */
|
||||
client_data->ec_dev->priv = client_data->cros_ish_cl;
|
||||
dev = cl_data_to_dev(client_data);
|
||||
dev->driver_data = client_data->ec_dev;
|
||||
|
||||
dev_info(cl_data_to_dev(client_data), "Chrome EC ISH reset done\n");
|
||||
|
||||
up_write(&init_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* cros_ec_ishtp_probe() - ISHTP client driver probe callback
|
||||
* @cl_device: ISHTP client device instance
|
||||
*
|
||||
* Return: 0 for success, negative error code for failure.
|
||||
*/
|
||||
static int cros_ec_ishtp_probe(struct ishtp_cl_device *cl_device)
|
||||
{
|
||||
int rv;
|
||||
struct ishtp_cl *cros_ish_cl;
|
||||
struct ishtp_cl_data *client_data =
|
||||
devm_kzalloc(ishtp_device(cl_device),
|
||||
sizeof(*client_data), GFP_KERNEL);
|
||||
if (!client_data)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Lock for initialization to complete */
|
||||
down_write(&init_lock);
|
||||
|
||||
cros_ish_cl = ishtp_cl_allocate(cl_device);
|
||||
if (!cros_ish_cl) {
|
||||
rv = -ENOMEM;
|
||||
goto end_ishtp_cl_alloc_error;
|
||||
}
|
||||
|
||||
ishtp_set_drvdata(cl_device, cros_ish_cl);
|
||||
ishtp_set_client_data(cros_ish_cl, client_data);
|
||||
client_data->cros_ish_cl = cros_ish_cl;
|
||||
client_data->cl_device = cl_device;
|
||||
|
||||
init_waitqueue_head(&client_data->response.wait_queue);
|
||||
|
||||
INIT_WORK(&client_data->work_ishtp_reset,
|
||||
reset_handler);
|
||||
INIT_WORK(&client_data->work_ec_evt,
|
||||
ish_evt_handler);
|
||||
|
||||
rv = cros_ish_init(cros_ish_cl);
|
||||
if (rv)
|
||||
goto end_ishtp_cl_init_error;
|
||||
|
||||
ishtp_get_device(cl_device);
|
||||
|
||||
up_write(&init_lock);
|
||||
|
||||
/* Register croc_ec_dev mfd */
|
||||
rv = cros_ec_dev_init(client_data);
|
||||
if (rv)
|
||||
goto end_cros_ec_dev_init_error;
|
||||
|
||||
return 0;
|
||||
|
||||
end_cros_ec_dev_init_error:
|
||||
ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_DISCONNECTING);
|
||||
ishtp_cl_disconnect(cros_ish_cl);
|
||||
ishtp_cl_unlink(cros_ish_cl);
|
||||
ishtp_cl_flush_queues(cros_ish_cl);
|
||||
ishtp_put_device(cl_device);
|
||||
end_ishtp_cl_init_error:
|
||||
ishtp_cl_free(cros_ish_cl);
|
||||
end_ishtp_cl_alloc_error:
|
||||
up_write(&init_lock);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* cros_ec_ishtp_remove() - ISHTP client driver remove callback
|
||||
* @cl_device: ISHTP client device instance
|
||||
*
|
||||
* Return: 0
|
||||
*/
|
||||
static int cros_ec_ishtp_remove(struct ishtp_cl_device *cl_device)
|
||||
{
|
||||
struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
|
||||
struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
|
||||
|
||||
cancel_work_sync(&client_data->work_ishtp_reset);
|
||||
cancel_work_sync(&client_data->work_ec_evt);
|
||||
cros_ish_deinit(cros_ish_cl);
|
||||
ishtp_put_device(cl_device);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cros_ec_ishtp_reset() - ISHTP client driver reset callback
|
||||
* @cl_device: ISHTP client device instance
|
||||
*
|
||||
* Return: 0
|
||||
*/
|
||||
static int cros_ec_ishtp_reset(struct ishtp_cl_device *cl_device)
|
||||
{
|
||||
struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
|
||||
struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
|
||||
|
||||
schedule_work(&client_data->work_ishtp_reset);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cros_ec_ishtp_suspend() - ISHTP client driver suspend callback
|
||||
* @device: device instance
|
||||
*
|
||||
* Return: 0 for success, negative error code for failure.
|
||||
*/
|
||||
static int __maybe_unused cros_ec_ishtp_suspend(struct device *device)
|
||||
{
|
||||
struct ishtp_cl_device *cl_device = dev_get_drvdata(device);
|
||||
struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
|
||||
struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
|
||||
|
||||
return cros_ec_suspend(client_data->ec_dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* cros_ec_ishtp_resume() - ISHTP client driver resume callback
|
||||
* @device: device instance
|
||||
*
|
||||
* Return: 0 for success, negative error code for failure.
|
||||
*/
|
||||
static int __maybe_unused cros_ec_ishtp_resume(struct device *device)
|
||||
{
|
||||
struct ishtp_cl_device *cl_device = dev_get_drvdata(device);
|
||||
struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device);
|
||||
struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
|
||||
|
||||
return cros_ec_resume(client_data->ec_dev);
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(cros_ec_ishtp_pm_ops, cros_ec_ishtp_suspend,
|
||||
cros_ec_ishtp_resume);
|
||||
|
||||
static struct ishtp_cl_driver cros_ec_ishtp_driver = {
|
||||
.name = "cros_ec_ishtp",
|
||||
.guid = &cros_ish_guid,
|
||||
.probe = cros_ec_ishtp_probe,
|
||||
.remove = cros_ec_ishtp_remove,
|
||||
.reset = cros_ec_ishtp_reset,
|
||||
.driver = {
|
||||
.pm = &cros_ec_ishtp_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init cros_ec_ishtp_mod_init(void)
|
||||
{
|
||||
return ishtp_cl_driver_register(&cros_ec_ishtp_driver, THIS_MODULE);
|
||||
}
|
||||
|
||||
static void __exit cros_ec_ishtp_mod_exit(void)
|
||||
{
|
||||
ishtp_cl_driver_unregister(&cros_ec_ishtp_driver);
|
||||
}
|
||||
|
||||
module_init(cros_ec_ishtp_mod_init);
|
||||
module_exit(cros_ec_ishtp_mod_exit);
|
||||
|
||||
MODULE_DESCRIPTION("ChromeOS EC ISHTP Client Driver");
|
||||
MODULE_AUTHOR("Rushikesh S Kadam <rushikesh.s.kadam@intel.com>");
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("ishtp:*");
|
@@ -547,7 +547,7 @@ static struct attribute *__lb_cmds_attrs[] = {
|
||||
NULL,
|
||||
};
|
||||
|
||||
struct attribute_group cros_ec_lightbar_attr_group = {
|
||||
static struct attribute_group cros_ec_lightbar_attr_group = {
|
||||
.name = "lightbar",
|
||||
.attrs = __lb_cmds_attrs,
|
||||
};
|
||||
@@ -600,7 +600,7 @@ static int cros_ec_lightbar_remove(struct platform_device *pd)
|
||||
|
||||
static int __maybe_unused cros_ec_lightbar_resume(struct device *dev)
|
||||
{
|
||||
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev);
|
||||
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
|
||||
|
||||
if (userspace_control)
|
||||
return 0;
|
||||
@@ -610,7 +610,7 @@ static int __maybe_unused cros_ec_lightbar_resume(struct device *dev)
|
||||
|
||||
static int __maybe_unused cros_ec_lightbar_suspend(struct device *dev)
|
||||
{
|
||||
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev);
|
||||
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
|
||||
|
||||
if (userspace_control)
|
||||
return 0;
|
||||
|
@@ -23,7 +23,7 @@
|
||||
#include <linux/printk.h>
|
||||
#include <linux/suspend.h>
|
||||
|
||||
#include "cros_ec_lpc_reg.h"
|
||||
#include "cros_ec_lpc_mec.h"
|
||||
|
||||
#define DRV_NAME "cros_ec_lpcs"
|
||||
#define ACPI_DRV_NAME "GOOG0004"
|
||||
@@ -31,6 +31,96 @@
|
||||
/* True if ACPI device is present */
|
||||
static bool cros_ec_lpc_acpi_device_found;
|
||||
|
||||
/**
|
||||
* struct lpc_driver_ops - LPC driver operations
|
||||
* @read: Copy length bytes from EC address offset into buffer dest. Returns
|
||||
* the 8-bit checksum of all bytes read.
|
||||
* @write: Copy length bytes from buffer msg into EC address offset. Returns
|
||||
* the 8-bit checksum of all bytes written.
|
||||
*/
|
||||
struct lpc_driver_ops {
|
||||
u8 (*read)(unsigned int offset, unsigned int length, u8 *dest);
|
||||
u8 (*write)(unsigned int offset, unsigned int length, const u8 *msg);
|
||||
};
|
||||
|
||||
static struct lpc_driver_ops cros_ec_lpc_ops = { };
|
||||
|
||||
/*
|
||||
* A generic instance of the read function of struct lpc_driver_ops, used for
|
||||
* the LPC EC.
|
||||
*/
|
||||
static u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length,
|
||||
u8 *dest)
|
||||
{
|
||||
int sum = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < length; ++i) {
|
||||
dest[i] = inb(offset + i);
|
||||
sum += dest[i];
|
||||
}
|
||||
|
||||
/* Return checksum of all bytes read */
|
||||
return sum;
|
||||
}
|
||||
|
||||
/*
|
||||
* A generic instance of the write function of struct lpc_driver_ops, used for
|
||||
* the LPC EC.
|
||||
*/
|
||||
static u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length,
|
||||
const u8 *msg)
|
||||
{
|
||||
int sum = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < length; ++i) {
|
||||
outb(msg[i], offset + i);
|
||||
sum += msg[i];
|
||||
}
|
||||
|
||||
/* Return checksum of all bytes written */
|
||||
return sum;
|
||||
}
|
||||
|
||||
/*
|
||||
* An instance of the read function of struct lpc_driver_ops, used for the
|
||||
* MEC variant of LPC EC.
|
||||
*/
|
||||
static u8 cros_ec_lpc_mec_read_bytes(unsigned int offset, unsigned int length,
|
||||
u8 *dest)
|
||||
{
|
||||
int in_range = cros_ec_lpc_mec_in_range(offset, length);
|
||||
|
||||
if (in_range < 0)
|
||||
return 0;
|
||||
|
||||
return in_range ?
|
||||
cros_ec_lpc_io_bytes_mec(MEC_IO_READ,
|
||||
offset - EC_HOST_CMD_REGION0,
|
||||
length, dest) :
|
||||
cros_ec_lpc_read_bytes(offset, length, dest);
|
||||
}
|
||||
|
||||
/*
|
||||
* An instance of the write function of struct lpc_driver_ops, used for the
|
||||
* MEC variant of LPC EC.
|
||||
*/
|
||||
static u8 cros_ec_lpc_mec_write_bytes(unsigned int offset, unsigned int length,
|
||||
const u8 *msg)
|
||||
{
|
||||
int in_range = cros_ec_lpc_mec_in_range(offset, length);
|
||||
|
||||
if (in_range < 0)
|
||||
return 0;
|
||||
|
||||
return in_range ?
|
||||
cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE,
|
||||
offset - EC_HOST_CMD_REGION0,
|
||||
length, (u8 *)msg) :
|
||||
cros_ec_lpc_write_bytes(offset, length, msg);
|
||||
}
|
||||
|
||||
static int ec_response_timed_out(void)
|
||||
{
|
||||
unsigned long one_second = jiffies + HZ;
|
||||
@@ -38,7 +128,7 @@ static int ec_response_timed_out(void)
|
||||
|
||||
usleep_range(200, 300);
|
||||
do {
|
||||
if (!(cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_CMD, 1, &data) &
|
||||
if (!(cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_CMD, 1, &data) &
|
||||
EC_LPC_STATUS_BUSY_MASK))
|
||||
return 0;
|
||||
usleep_range(100, 200);
|
||||
@@ -58,11 +148,11 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec,
|
||||
ret = cros_ec_prepare_tx(ec, msg);
|
||||
|
||||
/* Write buffer */
|
||||
cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_PACKET, ret, ec->dout);
|
||||
cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_PACKET, ret, ec->dout);
|
||||
|
||||
/* Here we go */
|
||||
sum = EC_COMMAND_PROTOCOL_3;
|
||||
cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_CMD, 1, &sum);
|
||||
cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_CMD, 1, &sum);
|
||||
|
||||
if (ec_response_timed_out()) {
|
||||
dev_warn(ec->dev, "EC responsed timed out\n");
|
||||
@@ -71,15 +161,15 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec,
|
||||
}
|
||||
|
||||
/* Check result */
|
||||
msg->result = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_DATA, 1, &sum);
|
||||
msg->result = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_DATA, 1, &sum);
|
||||
ret = cros_ec_check_result(ec, msg);
|
||||
if (ret)
|
||||
goto done;
|
||||
|
||||
/* Read back response */
|
||||
dout = (u8 *)&response;
|
||||
sum = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PACKET, sizeof(response),
|
||||
dout);
|
||||
sum = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET, sizeof(response),
|
||||
dout);
|
||||
|
||||
msg->result = response.result;
|
||||
|
||||
@@ -92,9 +182,9 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec,
|
||||
}
|
||||
|
||||
/* Read response and process checksum */
|
||||
sum += cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PACKET +
|
||||
sizeof(response), response.data_len,
|
||||
msg->data);
|
||||
sum += cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET +
|
||||
sizeof(response), response.data_len,
|
||||
msg->data);
|
||||
|
||||
if (sum) {
|
||||
dev_err(ec->dev,
|
||||
@@ -134,17 +224,17 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec,
|
||||
sum = msg->command + args.flags + args.command_version + args.data_size;
|
||||
|
||||
/* Copy data and update checksum */
|
||||
sum += cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_PARAM, msg->outsize,
|
||||
msg->data);
|
||||
sum += cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_PARAM, msg->outsize,
|
||||
msg->data);
|
||||
|
||||
/* Finalize checksum and write args */
|
||||
args.checksum = sum;
|
||||
cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_ARGS, sizeof(args),
|
||||
(u8 *)&args);
|
||||
cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_ARGS, sizeof(args),
|
||||
(u8 *)&args);
|
||||
|
||||
/* Here we go */
|
||||
sum = msg->command;
|
||||
cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_CMD, 1, &sum);
|
||||
cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_CMD, 1, &sum);
|
||||
|
||||
if (ec_response_timed_out()) {
|
||||
dev_warn(ec->dev, "EC responsed timed out\n");
|
||||
@@ -153,14 +243,13 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec,
|
||||
}
|
||||
|
||||
/* Check result */
|
||||
msg->result = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_DATA, 1, &sum);
|
||||
msg->result = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_DATA, 1, &sum);
|
||||
ret = cros_ec_check_result(ec, msg);
|
||||
if (ret)
|
||||
goto done;
|
||||
|
||||
/* Read back args */
|
||||
cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_ARGS, sizeof(args),
|
||||
(u8 *)&args);
|
||||
cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_ARGS, sizeof(args), (u8 *)&args);
|
||||
|
||||
if (args.data_size > msg->insize) {
|
||||
dev_err(ec->dev,
|
||||
@@ -174,8 +263,8 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec,
|
||||
sum = msg->command + args.flags + args.command_version + args.data_size;
|
||||
|
||||
/* Read response and update checksum */
|
||||
sum += cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PARAM, args.data_size,
|
||||
msg->data);
|
||||
sum += cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PARAM, args.data_size,
|
||||
msg->data);
|
||||
|
||||
/* Verify checksum */
|
||||
if (args.checksum != sum) {
|
||||
@@ -205,13 +294,13 @@ static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset,
|
||||
|
||||
/* fixed length */
|
||||
if (bytes) {
|
||||
cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + offset, bytes, s);
|
||||
cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + offset, bytes, s);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/* string */
|
||||
for (; i < EC_MEMMAP_SIZE; i++, s++) {
|
||||
cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + i, 1, s);
|
||||
cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + i, 1, s);
|
||||
cnt++;
|
||||
if (!*s)
|
||||
break;
|
||||
@@ -248,10 +337,25 @@ static int cros_ec_lpc_probe(struct platform_device *pdev)
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, buf);
|
||||
/*
|
||||
* Read the mapped ID twice, the first one is assuming the
|
||||
* EC is a Microchip Embedded Controller (MEC) variant, if the
|
||||
* protocol fails, fallback to the non MEC variant and try to
|
||||
* read again the ID.
|
||||
*/
|
||||
cros_ec_lpc_ops.read = cros_ec_lpc_mec_read_bytes;
|
||||
cros_ec_lpc_ops.write = cros_ec_lpc_mec_write_bytes;
|
||||
cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, buf);
|
||||
if (buf[0] != 'E' || buf[1] != 'C') {
|
||||
dev_err(dev, "EC ID not detected\n");
|
||||
return -ENODEV;
|
||||
/* Re-assign read/write operations for the non MEC variant */
|
||||
cros_ec_lpc_ops.read = cros_ec_lpc_read_bytes;
|
||||
cros_ec_lpc_ops.write = cros_ec_lpc_write_bytes;
|
||||
cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2,
|
||||
buf);
|
||||
if (buf[0] != 'E' || buf[1] != 'C') {
|
||||
dev_err(dev, "EC ID not detected\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
if (!devm_request_region(dev, EC_HOST_CMD_REGION0,
|
||||
@@ -405,7 +509,7 @@ static int cros_ec_lpc_resume(struct device *dev)
|
||||
}
|
||||
#endif
|
||||
|
||||
const struct dev_pm_ops cros_ec_lpc_pm_ops = {
|
||||
static const struct dev_pm_ops cros_ec_lpc_pm_ops = {
|
||||
SET_LATE_SYSTEM_SLEEP_PM_OPS(cros_ec_lpc_suspend, cros_ec_lpc_resume)
|
||||
};
|
||||
|
||||
@@ -446,13 +550,14 @@ static int __init cros_ec_lpc_init(void)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
cros_ec_lpc_reg_init();
|
||||
cros_ec_lpc_mec_init(EC_HOST_CMD_REGION0,
|
||||
EC_LPC_ADDR_MEMMAP + EC_MEMMAP_SIZE);
|
||||
|
||||
/* Register the driver */
|
||||
ret = platform_driver_register(&cros_ec_lpc_driver);
|
||||
if (ret) {
|
||||
pr_err(DRV_NAME ": can't register driver: %d\n", ret);
|
||||
cros_ec_lpc_reg_destroy();
|
||||
cros_ec_lpc_mec_destroy();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -462,7 +567,7 @@ static int __init cros_ec_lpc_init(void)
|
||||
if (ret) {
|
||||
pr_err(DRV_NAME ": can't register device: %d\n", ret);
|
||||
platform_driver_unregister(&cros_ec_lpc_driver);
|
||||
cros_ec_lpc_reg_destroy();
|
||||
cros_ec_lpc_mec_destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,7 +579,7 @@ static void __exit cros_ec_lpc_exit(void)
|
||||
if (!cros_ec_lpc_acpi_device_found)
|
||||
platform_device_unregister(&cros_ec_lpc_device);
|
||||
platform_driver_unregister(&cros_ec_lpc_driver);
|
||||
cros_ec_lpc_reg_destroy();
|
||||
cros_ec_lpc_mec_destroy();
|
||||
}
|
||||
|
||||
module_init(cros_ec_lpc_init);
|
||||
|
@@ -17,12 +17,10 @@
|
||||
static struct mutex io_mutex;
|
||||
static u16 mec_emi_base, mec_emi_end;
|
||||
|
||||
/*
|
||||
* cros_ec_lpc_mec_emi_write_address
|
||||
/**
|
||||
* cros_ec_lpc_mec_emi_write_address() - Initialize EMI at a given address.
|
||||
*
|
||||
* Initialize EMI read / write at a given address.
|
||||
*
|
||||
* @addr: Starting read / write address
|
||||
* @addr: Starting read / write address
|
||||
* @access_type: Type of access, typically 32-bit auto-increment
|
||||
*/
|
||||
static void cros_ec_lpc_mec_emi_write_address(u16 addr,
|
||||
@@ -61,15 +59,15 @@ int cros_ec_lpc_mec_in_range(unsigned int offset, unsigned int length)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* cros_ec_lpc_io_bytes_mec - Read / write bytes to MEC EMI port
|
||||
/**
|
||||
* cros_ec_lpc_io_bytes_mec() - Read / write bytes to MEC EMI port.
|
||||
*
|
||||
* @io_type: MEC_IO_READ or MEC_IO_WRITE, depending on request
|
||||
* @offset: Base read / write address
|
||||
* @length: Number of bytes to read / write
|
||||
* @buf: Destination / source buffer
|
||||
*
|
||||
* @return 8-bit checksum of all bytes read / written
|
||||
* Return: 8-bit checksum of all bytes read / written
|
||||
*/
|
||||
u8 cros_ec_lpc_io_bytes_mec(enum cros_ec_lpc_mec_io_type io_type,
|
||||
unsigned int offset, unsigned int length,
|
||||
|
@@ -1,101 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// LPC interface for ChromeOS Embedded Controller
|
||||
//
|
||||
// Copyright (C) 2016 Google, Inc
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/mfd/cros_ec.h>
|
||||
#include <linux/mfd/cros_ec_commands.h>
|
||||
|
||||
#include "cros_ec_lpc_mec.h"
|
||||
|
||||
static u8 lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest)
|
||||
{
|
||||
int i;
|
||||
int sum = 0;
|
||||
|
||||
for (i = 0; i < length; ++i) {
|
||||
dest[i] = inb(offset + i);
|
||||
sum += dest[i];
|
||||
}
|
||||
|
||||
/* Return checksum of all bytes read */
|
||||
return sum;
|
||||
}
|
||||
|
||||
static u8 lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg)
|
||||
{
|
||||
int i;
|
||||
int sum = 0;
|
||||
|
||||
for (i = 0; i < length; ++i) {
|
||||
outb(msg[i], offset + i);
|
||||
sum += msg[i];
|
||||
}
|
||||
|
||||
/* Return checksum of all bytes written */
|
||||
return sum;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_CROS_EC_LPC_MEC
|
||||
|
||||
u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest)
|
||||
{
|
||||
int in_range = cros_ec_lpc_mec_in_range(offset, length);
|
||||
|
||||
if (in_range < 0)
|
||||
return 0;
|
||||
|
||||
return in_range ?
|
||||
cros_ec_lpc_io_bytes_mec(MEC_IO_READ,
|
||||
offset - EC_HOST_CMD_REGION0,
|
||||
length, dest) :
|
||||
lpc_read_bytes(offset, length, dest);
|
||||
}
|
||||
|
||||
u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg)
|
||||
{
|
||||
int in_range = cros_ec_lpc_mec_in_range(offset, length);
|
||||
|
||||
if (in_range < 0)
|
||||
return 0;
|
||||
|
||||
return in_range ?
|
||||
cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE,
|
||||
offset - EC_HOST_CMD_REGION0,
|
||||
length, msg) :
|
||||
lpc_write_bytes(offset, length, msg);
|
||||
}
|
||||
|
||||
void cros_ec_lpc_reg_init(void)
|
||||
{
|
||||
cros_ec_lpc_mec_init(EC_HOST_CMD_REGION0,
|
||||
EC_LPC_ADDR_MEMMAP + EC_MEMMAP_SIZE);
|
||||
}
|
||||
|
||||
void cros_ec_lpc_reg_destroy(void)
|
||||
{
|
||||
cros_ec_lpc_mec_destroy();
|
||||
}
|
||||
|
||||
#else /* CONFIG_CROS_EC_LPC_MEC */
|
||||
|
||||
u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest)
|
||||
{
|
||||
return lpc_read_bytes(offset, length, dest);
|
||||
}
|
||||
|
||||
u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg)
|
||||
{
|
||||
return lpc_write_bytes(offset, length, msg);
|
||||
}
|
||||
|
||||
void cros_ec_lpc_reg_init(void)
|
||||
{
|
||||
}
|
||||
|
||||
void cros_ec_lpc_reg_destroy(void)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* CONFIG_CROS_EC_LPC_MEC */
|
@@ -1,45 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* LPC interface for ChromeOS Embedded Controller
|
||||
*
|
||||
* Copyright (C) 2016 Google, Inc
|
||||
*/
|
||||
|
||||
#ifndef __CROS_EC_LPC_REG_H
|
||||
#define __CROS_EC_LPC_REG_H
|
||||
|
||||
/**
|
||||
* cros_ec_lpc_read_bytes - Read bytes from a given LPC-mapped address.
|
||||
* Returns 8-bit checksum of all bytes read.
|
||||
*
|
||||
* @offset: Base read address
|
||||
* @length: Number of bytes to read
|
||||
* @dest: Destination buffer
|
||||
*/
|
||||
u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest);
|
||||
|
||||
/**
|
||||
* cros_ec_lpc_write_bytes - Write bytes to a given LPC-mapped address.
|
||||
* Returns 8-bit checksum of all bytes written.
|
||||
*
|
||||
* @offset: Base write address
|
||||
* @length: Number of bytes to write
|
||||
* @msg: Write data buffer
|
||||
*/
|
||||
u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg);
|
||||
|
||||
/**
|
||||
* cros_ec_lpc_reg_init
|
||||
*
|
||||
* Initialize register I/O.
|
||||
*/
|
||||
void cros_ec_lpc_reg_init(void);
|
||||
|
||||
/**
|
||||
* cros_ec_lpc_reg_destroy
|
||||
*
|
||||
* Cleanup reg I/O.
|
||||
*/
|
||||
void cros_ec_lpc_reg_destroy(void);
|
||||
|
||||
#endif /* __CROS_EC_LPC_REG_H */
|
@@ -12,7 +12,7 @@
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#include <uapi/linux/sched/types.h>
|
||||
|
||||
/* The header byte, which follows the preamble */
|
||||
#define EC_MSG_HEADER 0xec
|
||||
@@ -67,12 +67,14 @@
|
||||
* is sent when we want to turn on CS at the start of a transaction.
|
||||
* @end_of_msg_delay: used to set the delay_usecs on the spi_transfer that
|
||||
* is sent when we want to turn off CS at the end of a transaction.
|
||||
* @high_pri_worker: Used to schedule high priority work.
|
||||
*/
|
||||
struct cros_ec_spi {
|
||||
struct spi_device *spi;
|
||||
s64 last_transfer_ns;
|
||||
unsigned int start_of_msg_delay;
|
||||
unsigned int end_of_msg_delay;
|
||||
struct kthread_worker *high_pri_worker;
|
||||
};
|
||||
|
||||
typedef int (*cros_ec_xfer_fn_t) (struct cros_ec_device *ec_dev,
|
||||
@@ -89,7 +91,7 @@ typedef int (*cros_ec_xfer_fn_t) (struct cros_ec_device *ec_dev,
|
||||
*/
|
||||
|
||||
struct cros_ec_xfer_work_params {
|
||||
struct work_struct work;
|
||||
struct kthread_work work;
|
||||
cros_ec_xfer_fn_t fn;
|
||||
struct cros_ec_device *ec_dev;
|
||||
struct cros_ec_command *ec_msg;
|
||||
@@ -632,7 +634,7 @@ exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void cros_ec_xfer_high_pri_work(struct work_struct *work)
|
||||
static void cros_ec_xfer_high_pri_work(struct kthread_work *work)
|
||||
{
|
||||
struct cros_ec_xfer_work_params *params;
|
||||
|
||||
@@ -644,12 +646,14 @@ static int cros_ec_xfer_high_pri(struct cros_ec_device *ec_dev,
|
||||
struct cros_ec_command *ec_msg,
|
||||
cros_ec_xfer_fn_t fn)
|
||||
{
|
||||
struct cros_ec_xfer_work_params params;
|
||||
|
||||
INIT_WORK_ONSTACK(¶ms.work, cros_ec_xfer_high_pri_work);
|
||||
params.ec_dev = ec_dev;
|
||||
params.ec_msg = ec_msg;
|
||||
params.fn = fn;
|
||||
struct cros_ec_spi *ec_spi = ec_dev->priv;
|
||||
struct cros_ec_xfer_work_params params = {
|
||||
.work = KTHREAD_WORK_INIT(params.work,
|
||||
cros_ec_xfer_high_pri_work),
|
||||
.ec_dev = ec_dev,
|
||||
.ec_msg = ec_msg,
|
||||
.fn = fn,
|
||||
};
|
||||
|
||||
/*
|
||||
* This looks a bit ridiculous. Why do the work on a
|
||||
@@ -660,9 +664,8 @@ static int cros_ec_xfer_high_pri(struct cros_ec_device *ec_dev,
|
||||
* context switched out for too long and the EC giving up on
|
||||
* the transfer.
|
||||
*/
|
||||
queue_work(system_highpri_wq, ¶ms.work);
|
||||
flush_work(¶ms.work);
|
||||
destroy_work_on_stack(¶ms.work);
|
||||
kthread_queue_work(ec_spi->high_pri_worker, ¶ms.work);
|
||||
kthread_flush_work(¶ms.work);
|
||||
|
||||
return params.ret;
|
||||
}
|
||||
@@ -694,6 +697,40 @@ static void cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev)
|
||||
ec_spi->end_of_msg_delay = val;
|
||||
}
|
||||
|
||||
static void cros_ec_spi_high_pri_release(void *worker)
|
||||
{
|
||||
kthread_destroy_worker(worker);
|
||||
}
|
||||
|
||||
static int cros_ec_spi_devm_high_pri_alloc(struct device *dev,
|
||||
struct cros_ec_spi *ec_spi)
|
||||
{
|
||||
struct sched_param sched_priority = {
|
||||
.sched_priority = MAX_RT_PRIO - 1,
|
||||
};
|
||||
int err;
|
||||
|
||||
ec_spi->high_pri_worker =
|
||||
kthread_create_worker(0, "cros_ec_spi_high_pri");
|
||||
|
||||
if (IS_ERR(ec_spi->high_pri_worker)) {
|
||||
err = PTR_ERR(ec_spi->high_pri_worker);
|
||||
dev_err(dev, "Can't create cros_ec high pri worker: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = devm_add_action_or_reset(dev, cros_ec_spi_high_pri_release,
|
||||
ec_spi->high_pri_worker);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = sched_setscheduler_nocheck(ec_spi->high_pri_worker->task,
|
||||
SCHED_FIFO, &sched_priority);
|
||||
if (err)
|
||||
dev_err(dev, "Can't set cros_ec high pri priority: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cros_ec_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
struct device *dev = &spi->dev;
|
||||
@@ -703,6 +740,7 @@ static int cros_ec_spi_probe(struct spi_device *spi)
|
||||
|
||||
spi->bits_per_word = 8;
|
||||
spi->mode = SPI_MODE_0;
|
||||
spi->rt = true;
|
||||
err = spi_setup(spi);
|
||||
if (err < 0)
|
||||
return err;
|
||||
@@ -732,6 +770,10 @@ static int cros_ec_spi_probe(struct spi_device *spi)
|
||||
|
||||
ec_spi->last_transfer_ns = ktime_get_ns();
|
||||
|
||||
err = cros_ec_spi_devm_high_pri_alloc(dev, ec_spi);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = cros_ec_register(ec_dev);
|
||||
if (err) {
|
||||
dev_err(dev, "cannot register EC\n");
|
||||
@@ -777,7 +819,7 @@ MODULE_DEVICE_TABLE(spi, cros_ec_spi_id);
|
||||
static struct spi_driver cros_ec_driver_spi = {
|
||||
.driver = {
|
||||
.name = "cros-ec-spi",
|
||||
.of_match_table = of_match_ptr(cros_ec_spi_of_match),
|
||||
.of_match_table = cros_ec_spi_of_match,
|
||||
.pm = &cros_ec_spi_pm_ops,
|
||||
},
|
||||
.probe = cros_ec_spi_probe,
|
||||
|
@@ -335,7 +335,7 @@ static umode_t cros_ec_ctrl_visible(struct kobject *kobj,
|
||||
return a->mode;
|
||||
}
|
||||
|
||||
struct attribute_group cros_ec_attr_group = {
|
||||
static struct attribute_group cros_ec_attr_group = {
|
||||
.attrs = __ec_attrs,
|
||||
.is_visible = cros_ec_ctrl_visible,
|
||||
};
|
||||
|
@@ -101,7 +101,7 @@ static struct bin_attribute *cros_ec_vbc_bin_attrs[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
struct attribute_group cros_ec_vbc_attr_group = {
|
||||
static struct attribute_group cros_ec_vbc_attr_group = {
|
||||
.name = "vbc",
|
||||
.bin_attrs = cros_ec_vbc_bin_attrs,
|
||||
};
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config WILCO_EC
|
||||
tristate "ChromeOS Wilco Embedded Controller"
|
||||
depends on ACPI && X86 && CROS_EC_LPC && CROS_EC_LPC_MEC
|
||||
depends on ACPI && X86 && CROS_EC_LPC
|
||||
help
|
||||
If you say Y here, you get support for talking to the ChromeOS
|
||||
Wilco EC over an eSPI bus. This uses a simple byte-level protocol
|
||||
@@ -19,3 +19,19 @@ config WILCO_EC_DEBUGFS
|
||||
manipulation and allow for testing arbitrary commands. This
|
||||
interface is intended for debug only and will not be present
|
||||
on production devices.
|
||||
|
||||
config WILCO_EC_EVENTS
|
||||
tristate "Enable event forwarding from EC to userspace"
|
||||
depends on WILCO_EC
|
||||
help
|
||||
If you say Y here, you get support for the EC to send events
|
||||
(such as power state changes) to userspace. The EC sends the events
|
||||
over ACPI, and a driver queues up the events to be read by a
|
||||
userspace daemon from /dev/wilco_event using read() and poll().
|
||||
|
||||
config WILCO_EC_TELEMETRY
|
||||
tristate "Enable querying telemetry data from EC"
|
||||
depends on WILCO_EC
|
||||
help
|
||||
If you say Y here, you get support to query EC telemetry data from
|
||||
/dev/wilco_telem0 using write() and then read().
|
||||
|
@@ -1,6 +1,10 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
wilco_ec-objs := core.o mailbox.o
|
||||
wilco_ec-objs := core.o mailbox.o properties.o sysfs.o
|
||||
obj-$(CONFIG_WILCO_EC) += wilco_ec.o
|
||||
wilco_ec_debugfs-objs := debugfs.o
|
||||
obj-$(CONFIG_WILCO_EC_DEBUGFS) += wilco_ec_debugfs.o
|
||||
wilco_ec_events-objs := event.o
|
||||
obj-$(CONFIG_WILCO_EC_EVENTS) += wilco_ec_events.o
|
||||
wilco_ec_telem-objs := telemetry.o
|
||||
obj-$(CONFIG_WILCO_EC_TELEMETRY) += wilco_ec_telem.o
|
||||
|
@@ -52,9 +52,7 @@ static int wilco_ec_probe(struct platform_device *pdev)
|
||||
ec->dev = dev;
|
||||
mutex_init(&ec->mailbox_lock);
|
||||
|
||||
/* Largest data buffer size requirement is extended data response */
|
||||
ec->data_size = sizeof(struct wilco_ec_response) +
|
||||
EC_MAILBOX_DATA_SIZE_EXTENDED;
|
||||
ec->data_size = sizeof(struct wilco_ec_response) + EC_MAILBOX_DATA_SIZE;
|
||||
ec->data_buffer = devm_kzalloc(dev, ec->data_size, GFP_KERNEL);
|
||||
if (!ec->data_buffer)
|
||||
return -ENOMEM;
|
||||
@@ -89,8 +87,28 @@ static int wilco_ec_probe(struct platform_device *pdev)
|
||||
goto unregister_debugfs;
|
||||
}
|
||||
|
||||
ret = wilco_ec_add_sysfs(ec);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to create sysfs entries: %d", ret);
|
||||
goto unregister_rtc;
|
||||
}
|
||||
|
||||
/* Register child device that will be found by the telemetry driver. */
|
||||
ec->telem_pdev = platform_device_register_data(dev, "wilco_telem",
|
||||
PLATFORM_DEVID_AUTO,
|
||||
ec, sizeof(*ec));
|
||||
if (IS_ERR(ec->telem_pdev)) {
|
||||
dev_err(dev, "Failed to create telemetry platform device\n");
|
||||
ret = PTR_ERR(ec->telem_pdev);
|
||||
goto remove_sysfs;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
remove_sysfs:
|
||||
wilco_ec_remove_sysfs(ec);
|
||||
unregister_rtc:
|
||||
platform_device_unregister(ec->rtc_pdev);
|
||||
unregister_debugfs:
|
||||
if (ec->debugfs_pdev)
|
||||
platform_device_unregister(ec->debugfs_pdev);
|
||||
@@ -102,6 +120,8 @@ static int wilco_ec_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct wilco_ec_device *ec = platform_get_drvdata(pdev);
|
||||
|
||||
wilco_ec_remove_sysfs(ec);
|
||||
platform_device_unregister(ec->telem_pdev);
|
||||
platform_device_unregister(ec->rtc_pdev);
|
||||
if (ec->debugfs_pdev)
|
||||
platform_device_unregister(ec->debugfs_pdev);
|
||||
|
@@ -16,14 +16,14 @@
|
||||
|
||||
#define DRV_NAME "wilco-ec-debugfs"
|
||||
|
||||
/* The 256 raw bytes will take up more space when represented as a hex string */
|
||||
#define FORMATTED_BUFFER_SIZE (EC_MAILBOX_DATA_SIZE_EXTENDED * 4)
|
||||
/* The raw bytes will take up more space when represented as a hex string */
|
||||
#define FORMATTED_BUFFER_SIZE (EC_MAILBOX_DATA_SIZE * 4)
|
||||
|
||||
struct wilco_ec_debugfs {
|
||||
struct wilco_ec_device *ec;
|
||||
struct dentry *dir;
|
||||
size_t response_size;
|
||||
u8 raw_data[EC_MAILBOX_DATA_SIZE_EXTENDED];
|
||||
u8 raw_data[EC_MAILBOX_DATA_SIZE];
|
||||
u8 formatted_data[FORMATTED_BUFFER_SIZE];
|
||||
};
|
||||
static struct wilco_ec_debugfs *debug_info;
|
||||
@@ -124,12 +124,6 @@ static ssize_t raw_write(struct file *file, const char __user *user_buf,
|
||||
msg.response_data = debug_info->raw_data;
|
||||
msg.response_size = EC_MAILBOX_DATA_SIZE;
|
||||
|
||||
/* Telemetry commands use extended response data */
|
||||
if (msg.type == WILCO_EC_MSG_TELEMETRY_LONG) {
|
||||
msg.flags |= WILCO_EC_FLAG_EXTENDED_DATA;
|
||||
msg.response_size = EC_MAILBOX_DATA_SIZE_EXTENDED;
|
||||
}
|
||||
|
||||
ret = wilco_ec_mailbox(debug_info->ec, &msg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
581
drivers/platform/chrome/wilco_ec/event.c
Arquivo normal
581
drivers/platform/chrome/wilco_ec/event.c
Arquivo normal
@@ -0,0 +1,581 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* ACPI event handling for Wilco Embedded Controller
|
||||
*
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* The Wilco Embedded Controller can create custom events that
|
||||
* are not handled as standard ACPI objects. These events can
|
||||
* contain information about changes in EC controlled features,
|
||||
* such as errors and events in the dock or display. For example,
|
||||
* an event is triggered if the dock is plugged into a display
|
||||
* incorrectly. These events are needed for telemetry and
|
||||
* diagnostics reasons, and for possibly alerting the user.
|
||||
|
||||
* These events are triggered by the EC with an ACPI Notify(0x90),
|
||||
* and then the BIOS reads the event buffer from EC RAM via an
|
||||
* ACPI method. When the OS receives these events via ACPI,
|
||||
* it passes them along to this driver. The events are put into
|
||||
* a queue which can be read by a userspace daemon via a char device
|
||||
* that implements read() and poll(). The event queue acts as a
|
||||
* circular buffer of size 64, so if there are no userspace consumers
|
||||
* the kernel will not run out of memory. The char device will appear at
|
||||
* /dev/wilco_event{n}, where n is some small non-negative integer,
|
||||
* starting from 0. Standard ACPI events such as the battery getting
|
||||
* plugged/unplugged can also come through this path, but they are
|
||||
* dealt with via other paths, and are ignored here.
|
||||
|
||||
* To test, you can tail the binary data with
|
||||
* $ cat /dev/wilco_event0 | hexdump -ve '1/1 "%x\n"'
|
||||
* and then create an event by plugging/unplugging the battery.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
/* ACPI Notify event code indicating event data is available. */
|
||||
#define EC_ACPI_NOTIFY_EVENT 0x90
|
||||
/* ACPI Method to execute to retrieve event data buffer from the EC. */
|
||||
#define EC_ACPI_GET_EVENT "QSET"
|
||||
/* Maximum number of words in event data returned by the EC. */
|
||||
#define EC_ACPI_MAX_EVENT_WORDS 6
|
||||
#define EC_ACPI_MAX_EVENT_SIZE \
|
||||
(sizeof(struct ec_event) + (EC_ACPI_MAX_EVENT_WORDS) * sizeof(u16))
|
||||
|
||||
/* Node will appear in /dev/EVENT_DEV_NAME */
|
||||
#define EVENT_DEV_NAME "wilco_event"
|
||||
#define EVENT_CLASS_NAME EVENT_DEV_NAME
|
||||
#define DRV_NAME EVENT_DEV_NAME
|
||||
#define EVENT_DEV_NAME_FMT (EVENT_DEV_NAME "%d")
|
||||
static struct class event_class = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = EVENT_CLASS_NAME,
|
||||
};
|
||||
|
||||
/* Keep track of all the device numbers used. */
|
||||
#define EVENT_MAX_DEV 128
|
||||
static int event_major;
|
||||
static DEFINE_IDA(event_ida);
|
||||
|
||||
/* Size of circular queue of events. */
|
||||
#define MAX_NUM_EVENTS 64
|
||||
|
||||
/**
|
||||
* struct ec_event - Extended event returned by the EC.
|
||||
* @size: Number of 16bit words in structure after the size word.
|
||||
* @type: Extended event type, meaningless for us.
|
||||
* @event: Event data words. Max count is %EC_ACPI_MAX_EVENT_WORDS.
|
||||
*/
|
||||
struct ec_event {
|
||||
u16 size;
|
||||
u16 type;
|
||||
u16 event[0];
|
||||
} __packed;
|
||||
|
||||
#define ec_event_num_words(ev) (ev->size - 1)
|
||||
#define ec_event_size(ev) (sizeof(*ev) + (ec_event_num_words(ev) * sizeof(u16)))
|
||||
|
||||
/**
|
||||
* struct ec_event_queue - Circular queue for events.
|
||||
* @capacity: Number of elements the queue can hold.
|
||||
* @head: Next index to write to.
|
||||
* @tail: Next index to read from.
|
||||
* @entries: Array of events.
|
||||
*/
|
||||
struct ec_event_queue {
|
||||
int capacity;
|
||||
int head;
|
||||
int tail;
|
||||
struct ec_event *entries[0];
|
||||
};
|
||||
|
||||
/* Maximum number of events to store in ec_event_queue */
|
||||
static int queue_size = 64;
|
||||
module_param(queue_size, int, 0644);
|
||||
|
||||
static struct ec_event_queue *event_queue_new(int capacity)
|
||||
{
|
||||
struct ec_event_queue *q;
|
||||
|
||||
q = kzalloc(struct_size(q, entries, capacity), GFP_KERNEL);
|
||||
if (!q)
|
||||
return NULL;
|
||||
|
||||
q->capacity = capacity;
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
static inline bool event_queue_empty(struct ec_event_queue *q)
|
||||
{
|
||||
/* head==tail when both full and empty, but head==NULL when empty */
|
||||
return q->head == q->tail && !q->entries[q->head];
|
||||
}
|
||||
|
||||
static inline bool event_queue_full(struct ec_event_queue *q)
|
||||
{
|
||||
/* head==tail when both full and empty, but head!=NULL when full */
|
||||
return q->head == q->tail && q->entries[q->head];
|
||||
}
|
||||
|
||||
static struct ec_event *event_queue_pop(struct ec_event_queue *q)
|
||||
{
|
||||
struct ec_event *ev;
|
||||
|
||||
if (event_queue_empty(q))
|
||||
return NULL;
|
||||
|
||||
ev = q->entries[q->tail];
|
||||
q->entries[q->tail] = NULL;
|
||||
q->tail = (q->tail + 1) % q->capacity;
|
||||
|
||||
return ev;
|
||||
}
|
||||
|
||||
/*
|
||||
* If full, overwrite the oldest event and return it so the caller
|
||||
* can kfree it. If not full, return NULL.
|
||||
*/
|
||||
static struct ec_event *event_queue_push(struct ec_event_queue *q,
|
||||
struct ec_event *ev)
|
||||
{
|
||||
struct ec_event *popped = NULL;
|
||||
|
||||
if (event_queue_full(q))
|
||||
popped = event_queue_pop(q);
|
||||
q->entries[q->head] = ev;
|
||||
q->head = (q->head + 1) % q->capacity;
|
||||
|
||||
return popped;
|
||||
}
|
||||
|
||||
static void event_queue_free(struct ec_event_queue *q)
|
||||
{
|
||||
struct ec_event *event;
|
||||
|
||||
while ((event = event_queue_pop(q)) != NULL)
|
||||
kfree(event);
|
||||
|
||||
kfree(q);
|
||||
}
|
||||
|
||||
/**
|
||||
* struct event_device_data - Data for a Wilco EC device that responds to ACPI.
|
||||
* @events: Circular queue of EC events to be provided to userspace.
|
||||
* @queue_lock: Protect the queue from simultaneous read/writes.
|
||||
* @wq: Wait queue to notify processes when events are available or the
|
||||
* device has been removed.
|
||||
* @cdev: Char dev that userspace reads() and polls() from.
|
||||
* @dev: Device associated with the %cdev.
|
||||
* @exist: Has the device been not been removed? Once a device has been removed,
|
||||
* writes, reads, and new opens will fail.
|
||||
* @available: Guarantee only one client can open() file and read from queue.
|
||||
*
|
||||
* There will be one of these structs for each ACPI device registered. This data
|
||||
* is the queue of events received from ACPI that still need to be read from
|
||||
* userspace, the device and char device that userspace is using, a wait queue
|
||||
* used to notify different threads when something has changed, plus a flag
|
||||
* on whether the ACPI device has been removed.
|
||||
*/
|
||||
struct event_device_data {
|
||||
struct ec_event_queue *events;
|
||||
spinlock_t queue_lock;
|
||||
wait_queue_head_t wq;
|
||||
struct device dev;
|
||||
struct cdev cdev;
|
||||
bool exist;
|
||||
atomic_t available;
|
||||
};
|
||||
|
||||
/**
|
||||
* enqueue_events() - Place EC events in queue to be read by userspace.
|
||||
* @adev: Device the events came from.
|
||||
* @buf: Buffer of event data.
|
||||
* @length: Length of event data buffer.
|
||||
*
|
||||
* %buf contains a number of ec_event's, packed one after the other.
|
||||
* Each ec_event is of variable length. Start with the first event, copy it
|
||||
* into a persistent ec_event, store that entry in the queue, move on
|
||||
* to the next ec_event in buf, and repeat.
|
||||
*
|
||||
* Return: 0 on success or negative error code on failure.
|
||||
*/
|
||||
static int enqueue_events(struct acpi_device *adev, const u8 *buf, u32 length)
|
||||
{
|
||||
struct event_device_data *dev_data = adev->driver_data;
|
||||
struct ec_event *event, *queue_event, *old_event;
|
||||
size_t num_words, event_size;
|
||||
u32 offset = 0;
|
||||
|
||||
while (offset < length) {
|
||||
event = (struct ec_event *)(buf + offset);
|
||||
|
||||
num_words = ec_event_num_words(event);
|
||||
event_size = ec_event_size(event);
|
||||
if (num_words > EC_ACPI_MAX_EVENT_WORDS) {
|
||||
dev_err(&adev->dev, "Too many event words: %zu > %d\n",
|
||||
num_words, EC_ACPI_MAX_EVENT_WORDS);
|
||||
return -EOVERFLOW;
|
||||
}
|
||||
|
||||
/* Ensure event does not overflow the available buffer */
|
||||
if ((offset + event_size) > length) {
|
||||
dev_err(&adev->dev, "Event exceeds buffer: %zu > %d\n",
|
||||
offset + event_size, length);
|
||||
return -EOVERFLOW;
|
||||
}
|
||||
|
||||
/* Point to the next event in the buffer */
|
||||
offset += event_size;
|
||||
|
||||
/* Copy event into the queue */
|
||||
queue_event = kmemdup(event, event_size, GFP_KERNEL);
|
||||
if (!queue_event)
|
||||
return -ENOMEM;
|
||||
spin_lock(&dev_data->queue_lock);
|
||||
old_event = event_queue_push(dev_data->events, queue_event);
|
||||
spin_unlock(&dev_data->queue_lock);
|
||||
kfree(old_event);
|
||||
wake_up_interruptible(&dev_data->wq);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* event_device_notify() - Callback when EC generates an event over ACPI.
|
||||
* @adev: The device that the event is coming from.
|
||||
* @value: Value passed to Notify() in ACPI.
|
||||
*
|
||||
* This function will read the events from the device and enqueue them.
|
||||
*/
|
||||
static void event_device_notify(struct acpi_device *adev, u32 value)
|
||||
{
|
||||
struct acpi_buffer event_buffer = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
union acpi_object *obj;
|
||||
acpi_status status;
|
||||
|
||||
if (value != EC_ACPI_NOTIFY_EVENT) {
|
||||
dev_err(&adev->dev, "Invalid event: 0x%08x\n", value);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Execute ACPI method to get event data buffer. */
|
||||
status = acpi_evaluate_object(adev->handle, EC_ACPI_GET_EVENT,
|
||||
NULL, &event_buffer);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_err(&adev->dev, "Error executing ACPI method %s()\n",
|
||||
EC_ACPI_GET_EVENT);
|
||||
return;
|
||||
}
|
||||
|
||||
obj = (union acpi_object *)event_buffer.pointer;
|
||||
if (!obj) {
|
||||
dev_err(&adev->dev, "Nothing returned from %s()\n",
|
||||
EC_ACPI_GET_EVENT);
|
||||
return;
|
||||
}
|
||||
if (obj->type != ACPI_TYPE_BUFFER) {
|
||||
dev_err(&adev->dev, "Invalid object returned from %s()\n",
|
||||
EC_ACPI_GET_EVENT);
|
||||
kfree(obj);
|
||||
return;
|
||||
}
|
||||
if (obj->buffer.length < sizeof(struct ec_event)) {
|
||||
dev_err(&adev->dev, "Invalid buffer length %d from %s()\n",
|
||||
obj->buffer.length, EC_ACPI_GET_EVENT);
|
||||
kfree(obj);
|
||||
return;
|
||||
}
|
||||
|
||||
enqueue_events(adev, obj->buffer.pointer, obj->buffer.length);
|
||||
kfree(obj);
|
||||
}
|
||||
|
||||
static int event_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct event_device_data *dev_data;
|
||||
|
||||
dev_data = container_of(inode->i_cdev, struct event_device_data, cdev);
|
||||
if (!dev_data->exist)
|
||||
return -ENODEV;
|
||||
|
||||
if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0)
|
||||
return -EBUSY;
|
||||
|
||||
/* Increase refcount on device so dev_data is not freed */
|
||||
get_device(&dev_data->dev);
|
||||
stream_open(inode, filp);
|
||||
filp->private_data = dev_data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static __poll_t event_poll(struct file *filp, poll_table *wait)
|
||||
{
|
||||
struct event_device_data *dev_data = filp->private_data;
|
||||
__poll_t mask = 0;
|
||||
|
||||
poll_wait(filp, &dev_data->wq, wait);
|
||||
if (!dev_data->exist)
|
||||
return EPOLLHUP;
|
||||
if (!event_queue_empty(dev_data->events))
|
||||
mask |= EPOLLIN | EPOLLRDNORM | EPOLLPRI;
|
||||
return mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* event_read() - Callback for passing event data to userspace via read().
|
||||
* @filp: The file we are reading from.
|
||||
* @buf: Pointer to userspace buffer to fill with one event.
|
||||
* @count: Number of bytes requested. Must be at least EC_ACPI_MAX_EVENT_SIZE.
|
||||
* @pos: File position pointer, irrelevant since we don't support seeking.
|
||||
*
|
||||
* Removes the first event from the queue, places it in the passed buffer.
|
||||
*
|
||||
* If there are no events in the the queue, then one of two things happens,
|
||||
* depending on if the file was opened in nonblocking mode: If in nonblocking
|
||||
* mode, then return -EAGAIN to say there's no data. If in blocking mode, then
|
||||
* block until an event is available.
|
||||
*
|
||||
* Return: Number of bytes placed in buffer, negative error code on failure.
|
||||
*/
|
||||
static ssize_t event_read(struct file *filp, char __user *buf, size_t count,
|
||||
loff_t *pos)
|
||||
{
|
||||
struct event_device_data *dev_data = filp->private_data;
|
||||
struct ec_event *event;
|
||||
ssize_t n_bytes_written = 0;
|
||||
int err;
|
||||
|
||||
/* We only will give them the entire event at once */
|
||||
if (count != 0 && count < EC_ACPI_MAX_EVENT_SIZE)
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock(&dev_data->queue_lock);
|
||||
while (event_queue_empty(dev_data->events)) {
|
||||
spin_unlock(&dev_data->queue_lock);
|
||||
if (filp->f_flags & O_NONBLOCK)
|
||||
return -EAGAIN;
|
||||
|
||||
err = wait_event_interruptible(dev_data->wq,
|
||||
!event_queue_empty(dev_data->events) ||
|
||||
!dev_data->exist);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Device was removed as we waited? */
|
||||
if (!dev_data->exist)
|
||||
return -ENODEV;
|
||||
spin_lock(&dev_data->queue_lock);
|
||||
}
|
||||
event = event_queue_pop(dev_data->events);
|
||||
spin_unlock(&dev_data->queue_lock);
|
||||
n_bytes_written = ec_event_size(event);
|
||||
if (copy_to_user(buf, event, n_bytes_written))
|
||||
n_bytes_written = -EFAULT;
|
||||
kfree(event);
|
||||
|
||||
return n_bytes_written;
|
||||
}
|
||||
|
||||
static int event_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct event_device_data *dev_data = filp->private_data;
|
||||
|
||||
atomic_set(&dev_data->available, 1);
|
||||
put_device(&dev_data->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations event_fops = {
|
||||
.open = event_open,
|
||||
.poll = event_poll,
|
||||
.read = event_read,
|
||||
.release = event_release,
|
||||
.llseek = no_llseek,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
/**
|
||||
* free_device_data() - Callback to free the event_device_data structure.
|
||||
* @d: The device embedded in our device data, which we have been ref counting.
|
||||
*
|
||||
* This is called only after event_device_remove() has been called and all
|
||||
* userspace programs have called event_release() on all the open file
|
||||
* descriptors.
|
||||
*/
|
||||
static void free_device_data(struct device *d)
|
||||
{
|
||||
struct event_device_data *dev_data;
|
||||
|
||||
dev_data = container_of(d, struct event_device_data, dev);
|
||||
event_queue_free(dev_data->events);
|
||||
kfree(dev_data);
|
||||
}
|
||||
|
||||
static void hangup_device(struct event_device_data *dev_data)
|
||||
{
|
||||
dev_data->exist = false;
|
||||
/* Wake up the waiting processes so they can close. */
|
||||
wake_up_interruptible(&dev_data->wq);
|
||||
put_device(&dev_data->dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* event_device_add() - Callback when creating a new device.
|
||||
* @adev: ACPI device that we will be receiving events from.
|
||||
*
|
||||
* This finds a free minor number for the device, allocates and initializes
|
||||
* some device data, and creates a new device and char dev node.
|
||||
*
|
||||
* The device data is freed in free_device_data(), which is called when
|
||||
* %dev_data->dev is release()ed. This happens after all references to
|
||||
* %dev_data->dev are dropped, which happens once both event_device_remove()
|
||||
* has been called and every open()ed file descriptor has been release()ed.
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure.
|
||||
*/
|
||||
static int event_device_add(struct acpi_device *adev)
|
||||
{
|
||||
struct event_device_data *dev_data;
|
||||
int error, minor;
|
||||
|
||||
minor = ida_alloc_max(&event_ida, EVENT_MAX_DEV-1, GFP_KERNEL);
|
||||
if (minor < 0) {
|
||||
error = minor;
|
||||
dev_err(&adev->dev, "Failed to find minor number: %d\n", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL);
|
||||
if (!dev_data) {
|
||||
error = -ENOMEM;
|
||||
goto free_minor;
|
||||
}
|
||||
|
||||
/* Initialize the device data. */
|
||||
adev->driver_data = dev_data;
|
||||
dev_data->events = event_queue_new(queue_size);
|
||||
if (!dev_data->events) {
|
||||
kfree(dev_data);
|
||||
error = -ENOMEM;
|
||||
goto free_minor;
|
||||
}
|
||||
spin_lock_init(&dev_data->queue_lock);
|
||||
init_waitqueue_head(&dev_data->wq);
|
||||
dev_data->exist = true;
|
||||
atomic_set(&dev_data->available, 1);
|
||||
|
||||
/* Initialize the device. */
|
||||
dev_data->dev.devt = MKDEV(event_major, minor);
|
||||
dev_data->dev.class = &event_class;
|
||||
dev_data->dev.release = free_device_data;
|
||||
dev_set_name(&dev_data->dev, EVENT_DEV_NAME_FMT, minor);
|
||||
device_initialize(&dev_data->dev);
|
||||
|
||||
/* Initialize the character device, and add it to userspace. */
|
||||
cdev_init(&dev_data->cdev, &event_fops);
|
||||
error = cdev_device_add(&dev_data->cdev, &dev_data->dev);
|
||||
if (error)
|
||||
goto free_dev_data;
|
||||
|
||||
return 0;
|
||||
|
||||
free_dev_data:
|
||||
hangup_device(dev_data);
|
||||
free_minor:
|
||||
ida_simple_remove(&event_ida, minor);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int event_device_remove(struct acpi_device *adev)
|
||||
{
|
||||
struct event_device_data *dev_data = adev->driver_data;
|
||||
|
||||
cdev_device_del(&dev_data->cdev, &dev_data->dev);
|
||||
ida_simple_remove(&event_ida, MINOR(dev_data->dev.devt));
|
||||
hangup_device(dev_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id event_acpi_ids[] = {
|
||||
{ "GOOG000D", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, event_acpi_ids);
|
||||
|
||||
static struct acpi_driver event_driver = {
|
||||
.name = DRV_NAME,
|
||||
.class = DRV_NAME,
|
||||
.ids = event_acpi_ids,
|
||||
.ops = {
|
||||
.add = event_device_add,
|
||||
.notify = event_device_notify,
|
||||
.remove = event_device_remove,
|
||||
},
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int __init event_module_init(void)
|
||||
{
|
||||
dev_t dev_num = 0;
|
||||
int ret;
|
||||
|
||||
ret = class_register(&event_class);
|
||||
if (ret) {
|
||||
pr_err(DRV_NAME ": Failed registering class: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Request device numbers, starting with minor=0. Save the major num. */
|
||||
ret = alloc_chrdev_region(&dev_num, 0, EVENT_MAX_DEV, EVENT_DEV_NAME);
|
||||
if (ret) {
|
||||
pr_err(DRV_NAME ": Failed allocating dev numbers: %d\n", ret);
|
||||
goto destroy_class;
|
||||
}
|
||||
event_major = MAJOR(dev_num);
|
||||
|
||||
ret = acpi_bus_register_driver(&event_driver);
|
||||
if (ret < 0) {
|
||||
pr_err(DRV_NAME ": Failed registering driver: %d\n", ret);
|
||||
goto unregister_region;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
unregister_region:
|
||||
unregister_chrdev_region(MKDEV(event_major, 0), EVENT_MAX_DEV);
|
||||
destroy_class:
|
||||
class_unregister(&event_class);
|
||||
ida_destroy(&event_ida);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit event_module_exit(void)
|
||||
{
|
||||
acpi_bus_unregister_driver(&event_driver);
|
||||
unregister_chrdev_region(MKDEV(event_major, 0), EVENT_MAX_DEV);
|
||||
class_unregister(&event_class);
|
||||
ida_destroy(&event_ida);
|
||||
}
|
||||
|
||||
module_init(event_module_init);
|
||||
module_exit(event_module_exit);
|
||||
|
||||
MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
|
||||
MODULE_DESCRIPTION("Wilco EC ACPI event driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:" DRV_NAME);
|
@@ -119,7 +119,6 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec,
|
||||
struct wilco_ec_response *rs;
|
||||
u8 checksum;
|
||||
u8 flag;
|
||||
size_t size;
|
||||
|
||||
/* Write request header, then data */
|
||||
cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, 0, sizeof(*rq), (u8 *)rq);
|
||||
@@ -148,21 +147,11 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec,
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/*
|
||||
* The EC always returns either EC_MAILBOX_DATA_SIZE or
|
||||
* EC_MAILBOX_DATA_SIZE_EXTENDED bytes of data, so we need to
|
||||
* calculate the checksum on **all** of this data, even if we
|
||||
* won't use all of it.
|
||||
*/
|
||||
if (msg->flags & WILCO_EC_FLAG_EXTENDED_DATA)
|
||||
size = EC_MAILBOX_DATA_SIZE_EXTENDED;
|
||||
else
|
||||
size = EC_MAILBOX_DATA_SIZE;
|
||||
|
||||
/* Read back response */
|
||||
rs = ec->data_buffer;
|
||||
checksum = cros_ec_lpc_io_bytes_mec(MEC_IO_READ, 0,
|
||||
sizeof(*rs) + size, (u8 *)rs);
|
||||
sizeof(*rs) + EC_MAILBOX_DATA_SIZE,
|
||||
(u8 *)rs);
|
||||
if (checksum) {
|
||||
dev_dbg(ec->dev, "bad packet checksum 0x%02x\n", rs->checksum);
|
||||
return -EBADMSG;
|
||||
@@ -173,9 +162,9 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec,
|
||||
return -EBADMSG;
|
||||
}
|
||||
|
||||
if (rs->data_size != size) {
|
||||
dev_dbg(ec->dev, "unexpected packet size (%u != %zu)",
|
||||
rs->data_size, size);
|
||||
if (rs->data_size != EC_MAILBOX_DATA_SIZE) {
|
||||
dev_dbg(ec->dev, "unexpected packet size (%u != %u)",
|
||||
rs->data_size, EC_MAILBOX_DATA_SIZE);
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
|
132
drivers/platform/chrome/wilco_ec/properties.c
Arquivo normal
132
drivers/platform/chrome/wilco_ec/properties.c
Arquivo normal
@@ -0,0 +1,132 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright 2019 Google LLC
|
||||
*/
|
||||
|
||||
#include <linux/platform_data/wilco-ec.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/unaligned/le_memmove.h>
|
||||
|
||||
/* Operation code; what the EC should do with the property */
|
||||
enum ec_property_op {
|
||||
EC_OP_GET = 0,
|
||||
EC_OP_SET = 1,
|
||||
};
|
||||
|
||||
struct ec_property_request {
|
||||
u8 op; /* One of enum ec_property_op */
|
||||
u8 property_id[4]; /* The 32 bit PID is stored Little Endian */
|
||||
u8 length;
|
||||
u8 data[WILCO_EC_PROPERTY_MAX_SIZE];
|
||||
} __packed;
|
||||
|
||||
struct ec_property_response {
|
||||
u8 reserved[2];
|
||||
u8 op; /* One of enum ec_property_op */
|
||||
u8 property_id[4]; /* The 32 bit PID is stored Little Endian */
|
||||
u8 length;
|
||||
u8 data[WILCO_EC_PROPERTY_MAX_SIZE];
|
||||
} __packed;
|
||||
|
||||
static int send_property_msg(struct wilco_ec_device *ec,
|
||||
struct ec_property_request *rq,
|
||||
struct ec_property_response *rs)
|
||||
{
|
||||
struct wilco_ec_message ec_msg;
|
||||
int ret;
|
||||
|
||||
memset(&ec_msg, 0, sizeof(ec_msg));
|
||||
ec_msg.type = WILCO_EC_MSG_PROPERTY;
|
||||
ec_msg.request_data = rq;
|
||||
ec_msg.request_size = sizeof(*rq);
|
||||
ec_msg.response_data = rs;
|
||||
ec_msg.response_size = sizeof(*rs);
|
||||
|
||||
ret = wilco_ec_mailbox(ec, &ec_msg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (rs->op != rq->op)
|
||||
return -EBADMSG;
|
||||
if (memcmp(rq->property_id, rs->property_id, sizeof(rs->property_id)))
|
||||
return -EBADMSG;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int wilco_ec_get_property(struct wilco_ec_device *ec,
|
||||
struct wilco_ec_property_msg *prop_msg)
|
||||
{
|
||||
struct ec_property_request rq;
|
||||
struct ec_property_response rs;
|
||||
int ret;
|
||||
|
||||
memset(&rq, 0, sizeof(rq));
|
||||
rq.op = EC_OP_GET;
|
||||
put_unaligned_le32(prop_msg->property_id, rq.property_id);
|
||||
|
||||
ret = send_property_msg(ec, &rq, &rs);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
prop_msg->length = rs.length;
|
||||
memcpy(prop_msg->data, rs.data, rs.length);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wilco_ec_get_property);
|
||||
|
||||
int wilco_ec_set_property(struct wilco_ec_device *ec,
|
||||
struct wilco_ec_property_msg *prop_msg)
|
||||
{
|
||||
struct ec_property_request rq;
|
||||
struct ec_property_response rs;
|
||||
int ret;
|
||||
|
||||
memset(&rq, 0, sizeof(rq));
|
||||
rq.op = EC_OP_SET;
|
||||
put_unaligned_le32(prop_msg->property_id, rq.property_id);
|
||||
rq.length = prop_msg->length;
|
||||
memcpy(rq.data, prop_msg->data, prop_msg->length);
|
||||
|
||||
ret = send_property_msg(ec, &rq, &rs);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (rs.length != prop_msg->length)
|
||||
return -EBADMSG;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wilco_ec_set_property);
|
||||
|
||||
int wilco_ec_get_byte_property(struct wilco_ec_device *ec, u32 property_id,
|
||||
u8 *val)
|
||||
{
|
||||
struct wilco_ec_property_msg msg;
|
||||
int ret;
|
||||
|
||||
msg.property_id = property_id;
|
||||
|
||||
ret = wilco_ec_get_property(ec, &msg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (msg.length != 1)
|
||||
return -EBADMSG;
|
||||
|
||||
*val = msg.data[0];
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wilco_ec_get_byte_property);
|
||||
|
||||
int wilco_ec_set_byte_property(struct wilco_ec_device *ec, u32 property_id,
|
||||
u8 val)
|
||||
{
|
||||
struct wilco_ec_property_msg msg;
|
||||
|
||||
msg.property_id = property_id;
|
||||
msg.data[0] = val;
|
||||
msg.length = 1;
|
||||
|
||||
return wilco_ec_set_property(ec, &msg);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wilco_ec_set_byte_property);
|
156
drivers/platform/chrome/wilco_ec/sysfs.c
Arquivo normal
156
drivers/platform/chrome/wilco_ec/sysfs.c
Arquivo normal
@@ -0,0 +1,156 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* Sysfs properties to view and modify EC-controlled features on Wilco devices.
|
||||
* The entries will appear under /sys/bus/platform/devices/GOOG000C:00/
|
||||
*
|
||||
* See Documentation/ABI/testing/sysfs-platform-wilco-ec for more information.
|
||||
*/
|
||||
|
||||
#include <linux/platform_data/wilco-ec.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
#define CMD_KB_CMOS 0x7C
|
||||
#define SUB_CMD_KB_CMOS_AUTO_ON 0x03
|
||||
|
||||
struct boot_on_ac_request {
|
||||
u8 cmd; /* Always CMD_KB_CMOS */
|
||||
u8 reserved1;
|
||||
u8 sub_cmd; /* Always SUB_CMD_KB_CMOS_AUTO_ON */
|
||||
u8 reserved3to5[3];
|
||||
u8 val; /* Either 0 or 1 */
|
||||
u8 reserved7;
|
||||
} __packed;
|
||||
|
||||
#define CMD_EC_INFO 0x38
|
||||
enum get_ec_info_op {
|
||||
CMD_GET_EC_LABEL = 0,
|
||||
CMD_GET_EC_REV = 1,
|
||||
CMD_GET_EC_MODEL = 2,
|
||||
CMD_GET_EC_BUILD_DATE = 3,
|
||||
};
|
||||
|
||||
struct get_ec_info_req {
|
||||
u8 cmd; /* Always CMD_EC_INFO */
|
||||
u8 reserved;
|
||||
u8 op; /* One of enum get_ec_info_op */
|
||||
} __packed;
|
||||
|
||||
struct get_ec_info_resp {
|
||||
u8 reserved[2];
|
||||
char value[9]; /* __nonstring: might not be null terminated */
|
||||
} __packed;
|
||||
|
||||
static ssize_t boot_on_ac_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct wilco_ec_device *ec = dev_get_drvdata(dev);
|
||||
struct boot_on_ac_request rq;
|
||||
struct wilco_ec_message msg;
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
ret = kstrtou8(buf, 10, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (val > 1)
|
||||
return -EINVAL;
|
||||
|
||||
memset(&rq, 0, sizeof(rq));
|
||||
rq.cmd = CMD_KB_CMOS;
|
||||
rq.sub_cmd = SUB_CMD_KB_CMOS_AUTO_ON;
|
||||
rq.val = val;
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.type = WILCO_EC_MSG_LEGACY;
|
||||
msg.request_data = &rq;
|
||||
msg.request_size = sizeof(rq);
|
||||
ret = wilco_ec_mailbox(ec, &msg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_WO(boot_on_ac);
|
||||
|
||||
static ssize_t get_info(struct device *dev, char *buf, enum get_ec_info_op op)
|
||||
{
|
||||
struct wilco_ec_device *ec = dev_get_drvdata(dev);
|
||||
struct get_ec_info_req req = { .cmd = CMD_EC_INFO, .op = op };
|
||||
struct get_ec_info_resp resp;
|
||||
int ret;
|
||||
|
||||
struct wilco_ec_message msg = {
|
||||
.type = WILCO_EC_MSG_LEGACY,
|
||||
.request_data = &req,
|
||||
.request_size = sizeof(req),
|
||||
.response_data = &resp,
|
||||
.response_size = sizeof(resp),
|
||||
};
|
||||
|
||||
ret = wilco_ec_mailbox(ec, &msg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%.*s\n", (int)sizeof(resp.value),
|
||||
(char *)&resp.value);
|
||||
}
|
||||
|
||||
static ssize_t version_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return get_info(dev, buf, CMD_GET_EC_LABEL);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(version);
|
||||
|
||||
static ssize_t build_revision_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return get_info(dev, buf, CMD_GET_EC_REV);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(build_revision);
|
||||
|
||||
static ssize_t build_date_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return get_info(dev, buf, CMD_GET_EC_BUILD_DATE);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(build_date);
|
||||
|
||||
static ssize_t model_number_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return get_info(dev, buf, CMD_GET_EC_MODEL);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(model_number);
|
||||
|
||||
|
||||
static struct attribute *wilco_dev_attrs[] = {
|
||||
&dev_attr_boot_on_ac.attr,
|
||||
&dev_attr_build_date.attr,
|
||||
&dev_attr_build_revision.attr,
|
||||
&dev_attr_model_number.attr,
|
||||
&dev_attr_version.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group wilco_dev_attr_group = {
|
||||
.attrs = wilco_dev_attrs,
|
||||
};
|
||||
|
||||
int wilco_ec_add_sysfs(struct wilco_ec_device *ec)
|
||||
{
|
||||
return sysfs_create_group(&ec->dev->kobj, &wilco_dev_attr_group);
|
||||
}
|
||||
|
||||
void wilco_ec_remove_sysfs(struct wilco_ec_device *ec)
|
||||
{
|
||||
sysfs_remove_group(&ec->dev->kobj, &wilco_dev_attr_group);
|
||||
}
|
450
drivers/platform/chrome/wilco_ec/telemetry.c
Arquivo normal
450
drivers/platform/chrome/wilco_ec/telemetry.c
Arquivo normal
@@ -0,0 +1,450 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Telemetry communication for Wilco EC
|
||||
*
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* The Wilco Embedded Controller is able to send telemetry data
|
||||
* which is useful for enterprise applications. A daemon running on
|
||||
* the OS sends a command to the EC via a write() to a char device,
|
||||
* and can read the response with a read(). The write() request is
|
||||
* verified by the driver to ensure that it is performing only one
|
||||
* of the whitelisted commands, and that no extraneous data is
|
||||
* being transmitted to the EC. The response is passed directly
|
||||
* back to the reader with no modification.
|
||||
*
|
||||
* The character device will appear as /dev/wilco_telemN, where N
|
||||
* is some small non-negative integer, starting with 0. Only one
|
||||
* process may have the file descriptor open at a time. The calling
|
||||
* userspace program needs to keep the device file descriptor open
|
||||
* between the calls to write() and read() in order to preserve the
|
||||
* response. Up to 32 bytes will be available for reading.
|
||||
*
|
||||
* For testing purposes, try requesting the EC's firmware build
|
||||
* date, by sending the WILCO_EC_TELEM_GET_VERSION command with
|
||||
* argument index=3. i.e. write [0x38, 0x00, 0x03]
|
||||
* to the device node. An ASCII string of the build date is
|
||||
* returned.
|
||||
*/
|
||||
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_data/wilco-ec.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#define TELEM_DEV_NAME "wilco_telem"
|
||||
#define TELEM_CLASS_NAME TELEM_DEV_NAME
|
||||
#define DRV_NAME TELEM_DEV_NAME
|
||||
#define TELEM_DEV_NAME_FMT (TELEM_DEV_NAME "%d")
|
||||
static struct class telem_class = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = TELEM_CLASS_NAME,
|
||||
};
|
||||
|
||||
/* Keep track of all the device numbers used. */
|
||||
#define TELEM_MAX_DEV 128
|
||||
static int telem_major;
|
||||
static DEFINE_IDA(telem_ida);
|
||||
|
||||
/* EC telemetry command codes */
|
||||
#define WILCO_EC_TELEM_GET_LOG 0x99
|
||||
#define WILCO_EC_TELEM_GET_VERSION 0x38
|
||||
#define WILCO_EC_TELEM_GET_FAN_INFO 0x2E
|
||||
#define WILCO_EC_TELEM_GET_DIAG_INFO 0xFA
|
||||
#define WILCO_EC_TELEM_GET_TEMP_INFO 0x95
|
||||
#define WILCO_EC_TELEM_GET_TEMP_READ 0x2C
|
||||
#define WILCO_EC_TELEM_GET_BATT_EXT_INFO 0x07
|
||||
|
||||
#define TELEM_ARGS_SIZE_MAX 30
|
||||
|
||||
/**
|
||||
* struct wilco_ec_telem_request - Telemetry command and arguments sent to EC.
|
||||
* @command: One of WILCO_EC_TELEM_GET_* command codes.
|
||||
* @reserved: Must be 0.
|
||||
* @args: The first N bytes are one of telem_args_get_* structs, the rest is 0.
|
||||
*/
|
||||
struct wilco_ec_telem_request {
|
||||
u8 command;
|
||||
u8 reserved;
|
||||
u8 args[TELEM_ARGS_SIZE_MAX];
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* The following telem_args_get_* structs are embedded within the |args| field
|
||||
* of wilco_ec_telem_request.
|
||||
*/
|
||||
|
||||
struct telem_args_get_log {
|
||||
u8 log_type;
|
||||
u8 log_index;
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* Get a piece of info about the EC firmware version:
|
||||
* 0 = label
|
||||
* 1 = svn_rev
|
||||
* 2 = model_no
|
||||
* 3 = build_date
|
||||
* 4 = frio_version
|
||||
*/
|
||||
struct telem_args_get_version {
|
||||
u8 index;
|
||||
} __packed;
|
||||
|
||||
struct telem_args_get_fan_info {
|
||||
u8 command;
|
||||
u8 fan_number;
|
||||
u8 arg;
|
||||
} __packed;
|
||||
|
||||
struct telem_args_get_diag_info {
|
||||
u8 type;
|
||||
u8 sub_type;
|
||||
} __packed;
|
||||
|
||||
struct telem_args_get_temp_info {
|
||||
u8 command;
|
||||
u8 index;
|
||||
u8 field;
|
||||
u8 zone;
|
||||
} __packed;
|
||||
|
||||
struct telem_args_get_temp_read {
|
||||
u8 sensor_index;
|
||||
} __packed;
|
||||
|
||||
struct telem_args_get_batt_ext_info {
|
||||
u8 var_args[5];
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* check_telem_request() - Ensure that a request from userspace is valid.
|
||||
* @rq: Request buffer copied from userspace.
|
||||
* @size: Number of bytes copied from userspace.
|
||||
*
|
||||
* Return: 0 if valid, -EINVAL if bad command or reserved byte is non-zero,
|
||||
* -EMSGSIZE if the request is too long.
|
||||
*
|
||||
* We do not want to allow userspace to send arbitrary telemetry commands to
|
||||
* the EC. Therefore we check to ensure that
|
||||
* 1. The request follows the format of struct wilco_ec_telem_request.
|
||||
* 2. The supplied command code is one of the whitelisted commands.
|
||||
* 3. The request only contains the necessary data for the header and arguments.
|
||||
*/
|
||||
static int check_telem_request(struct wilco_ec_telem_request *rq,
|
||||
size_t size)
|
||||
{
|
||||
size_t max_size = offsetof(struct wilco_ec_telem_request, args);
|
||||
|
||||
if (rq->reserved)
|
||||
return -EINVAL;
|
||||
|
||||
switch (rq->command) {
|
||||
case WILCO_EC_TELEM_GET_LOG:
|
||||
max_size += sizeof(struct telem_args_get_log);
|
||||
break;
|
||||
case WILCO_EC_TELEM_GET_VERSION:
|
||||
max_size += sizeof(struct telem_args_get_version);
|
||||
break;
|
||||
case WILCO_EC_TELEM_GET_FAN_INFO:
|
||||
max_size += sizeof(struct telem_args_get_fan_info);
|
||||
break;
|
||||
case WILCO_EC_TELEM_GET_DIAG_INFO:
|
||||
max_size += sizeof(struct telem_args_get_diag_info);
|
||||
break;
|
||||
case WILCO_EC_TELEM_GET_TEMP_INFO:
|
||||
max_size += sizeof(struct telem_args_get_temp_info);
|
||||
break;
|
||||
case WILCO_EC_TELEM_GET_TEMP_READ:
|
||||
max_size += sizeof(struct telem_args_get_temp_read);
|
||||
break;
|
||||
case WILCO_EC_TELEM_GET_BATT_EXT_INFO:
|
||||
max_size += sizeof(struct telem_args_get_batt_ext_info);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return (size <= max_size) ? 0 : -EMSGSIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* struct telem_device_data - Data for a Wilco EC device that queries telemetry.
|
||||
* @cdev: Char dev that userspace reads and polls from.
|
||||
* @dev: Device associated with the %cdev.
|
||||
* @ec: Wilco EC that we will be communicating with using the mailbox interface.
|
||||
* @available: Boolean of if the device can be opened.
|
||||
*/
|
||||
struct telem_device_data {
|
||||
struct device dev;
|
||||
struct cdev cdev;
|
||||
struct wilco_ec_device *ec;
|
||||
atomic_t available;
|
||||
};
|
||||
|
||||
#define TELEM_RESPONSE_SIZE EC_MAILBOX_DATA_SIZE
|
||||
|
||||
/**
|
||||
* struct telem_session_data - Data that exists between open() and release().
|
||||
* @dev_data: Pointer to get back to the device data and EC.
|
||||
* @request: Command and arguments sent to EC.
|
||||
* @response: Response buffer of data from EC.
|
||||
* @has_msg: Is there data available to read from a previous write?
|
||||
*/
|
||||
struct telem_session_data {
|
||||
struct telem_device_data *dev_data;
|
||||
struct wilco_ec_telem_request request;
|
||||
u8 response[TELEM_RESPONSE_SIZE];
|
||||
bool has_msg;
|
||||
};
|
||||
|
||||
/**
|
||||
* telem_open() - Callback for when the device node is opened.
|
||||
* @inode: inode for this char device node.
|
||||
* @filp: file for this char device node.
|
||||
*
|
||||
* We need to ensure that after writing a command to the device,
|
||||
* the same userspace process reads the corresponding result.
|
||||
* Therefore, we increment a refcount on opening the device, so that
|
||||
* only one process can communicate with the EC at a time.
|
||||
*
|
||||
* Return: 0 on success, or negative error code on failure.
|
||||
*/
|
||||
static int telem_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct telem_device_data *dev_data;
|
||||
struct telem_session_data *sess_data;
|
||||
|
||||
/* Ensure device isn't already open */
|
||||
dev_data = container_of(inode->i_cdev, struct telem_device_data, cdev);
|
||||
if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0)
|
||||
return -EBUSY;
|
||||
|
||||
get_device(&dev_data->dev);
|
||||
|
||||
sess_data = kzalloc(sizeof(*sess_data), GFP_KERNEL);
|
||||
if (!sess_data) {
|
||||
atomic_set(&dev_data->available, 1);
|
||||
return -ENOMEM;
|
||||
}
|
||||
sess_data->dev_data = dev_data;
|
||||
sess_data->has_msg = false;
|
||||
|
||||
nonseekable_open(inode, filp);
|
||||
filp->private_data = sess_data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t telem_write(struct file *filp, const char __user *buf,
|
||||
size_t count, loff_t *pos)
|
||||
{
|
||||
struct telem_session_data *sess_data = filp->private_data;
|
||||
struct wilco_ec_message msg = {};
|
||||
int ret;
|
||||
|
||||
if (count > sizeof(sess_data->request))
|
||||
return -EMSGSIZE;
|
||||
if (copy_from_user(&sess_data->request, buf, count))
|
||||
return -EFAULT;
|
||||
ret = check_telem_request(&sess_data->request, count);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
memset(sess_data->response, 0, sizeof(sess_data->response));
|
||||
msg.type = WILCO_EC_MSG_TELEMETRY;
|
||||
msg.request_data = &sess_data->request;
|
||||
msg.request_size = sizeof(sess_data->request);
|
||||
msg.response_data = sess_data->response;
|
||||
msg.response_size = sizeof(sess_data->response);
|
||||
|
||||
ret = wilco_ec_mailbox(sess_data->dev_data->ec, &msg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret != sizeof(sess_data->response))
|
||||
return -EMSGSIZE;
|
||||
|
||||
sess_data->has_msg = true;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t telem_read(struct file *filp, char __user *buf, size_t count,
|
||||
loff_t *pos)
|
||||
{
|
||||
struct telem_session_data *sess_data = filp->private_data;
|
||||
|
||||
if (!sess_data->has_msg)
|
||||
return -ENODATA;
|
||||
if (count > sizeof(sess_data->response))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_to_user(buf, sess_data->response, count))
|
||||
return -EFAULT;
|
||||
|
||||
sess_data->has_msg = false;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int telem_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct telem_session_data *sess_data = filp->private_data;
|
||||
|
||||
atomic_set(&sess_data->dev_data->available, 1);
|
||||
put_device(&sess_data->dev_data->dev);
|
||||
kfree(sess_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations telem_fops = {
|
||||
.open = telem_open,
|
||||
.write = telem_write,
|
||||
.read = telem_read,
|
||||
.release = telem_release,
|
||||
.llseek = no_llseek,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
/**
|
||||
* telem_device_free() - Callback to free the telem_device_data structure.
|
||||
* @d: The device embedded in our device data, which we have been ref counting.
|
||||
*
|
||||
* Once all open file descriptors are closed and the device has been removed,
|
||||
* the refcount of the device will fall to 0 and this will be called.
|
||||
*/
|
||||
static void telem_device_free(struct device *d)
|
||||
{
|
||||
struct telem_device_data *dev_data;
|
||||
|
||||
dev_data = container_of(d, struct telem_device_data, dev);
|
||||
kfree(dev_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* telem_device_probe() - Callback when creating a new device.
|
||||
* @pdev: platform device that we will be receiving telems from.
|
||||
*
|
||||
* This finds a free minor number for the device, allocates and initializes
|
||||
* some device data, and creates a new device and char dev node.
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure.
|
||||
*/
|
||||
static int telem_device_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct telem_device_data *dev_data;
|
||||
int error, minor;
|
||||
|
||||
/* Get the next available device number */
|
||||
minor = ida_alloc_max(&telem_ida, TELEM_MAX_DEV-1, GFP_KERNEL);
|
||||
if (minor < 0) {
|
||||
error = minor;
|
||||
dev_err(&pdev->dev, "Failed to find minor number: %d", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL);
|
||||
if (!dev_data) {
|
||||
ida_simple_remove(&telem_ida, minor);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Initialize the device data */
|
||||
dev_data->ec = dev_get_platdata(&pdev->dev);
|
||||
atomic_set(&dev_data->available, 1);
|
||||
platform_set_drvdata(pdev, dev_data);
|
||||
|
||||
/* Initialize the device */
|
||||
dev_data->dev.devt = MKDEV(telem_major, minor);
|
||||
dev_data->dev.class = &telem_class;
|
||||
dev_data->dev.release = telem_device_free;
|
||||
dev_set_name(&dev_data->dev, TELEM_DEV_NAME_FMT, minor);
|
||||
device_initialize(&dev_data->dev);
|
||||
|
||||
/* Initialize the character device and add it to userspace */;
|
||||
cdev_init(&dev_data->cdev, &telem_fops);
|
||||
error = cdev_device_add(&dev_data->cdev, &dev_data->dev);
|
||||
if (error) {
|
||||
put_device(&dev_data->dev);
|
||||
ida_simple_remove(&telem_ida, minor);
|
||||
return error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int telem_device_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct telem_device_data *dev_data = platform_get_drvdata(pdev);
|
||||
|
||||
cdev_device_del(&dev_data->cdev, &dev_data->dev);
|
||||
put_device(&dev_data->dev);
|
||||
ida_simple_remove(&telem_ida, MINOR(dev_data->dev.devt));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver telem_driver = {
|
||||
.probe = telem_device_probe,
|
||||
.remove = telem_device_remove,
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init telem_module_init(void)
|
||||
{
|
||||
dev_t dev_num = 0;
|
||||
int ret;
|
||||
|
||||
ret = class_register(&telem_class);
|
||||
if (ret) {
|
||||
pr_err(DRV_NAME ": Failed registering class: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Request the kernel for device numbers, starting with minor=0 */
|
||||
ret = alloc_chrdev_region(&dev_num, 0, TELEM_MAX_DEV, TELEM_DEV_NAME);
|
||||
if (ret) {
|
||||
pr_err(DRV_NAME ": Failed allocating dev numbers: %d", ret);
|
||||
goto destroy_class;
|
||||
}
|
||||
telem_major = MAJOR(dev_num);
|
||||
|
||||
ret = platform_driver_register(&telem_driver);
|
||||
if (ret < 0) {
|
||||
pr_err(DRV_NAME ": Failed registering driver: %d\n", ret);
|
||||
goto unregister_region;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
unregister_region:
|
||||
unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
|
||||
destroy_class:
|
||||
class_unregister(&telem_class);
|
||||
ida_destroy(&telem_ida);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit telem_module_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&telem_driver);
|
||||
unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
|
||||
class_unregister(&telem_class);
|
||||
ida_destroy(&telem_ida);
|
||||
}
|
||||
|
||||
module_init(telem_module_init);
|
||||
module_exit(telem_module_exit);
|
||||
|
||||
MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
|
||||
MODULE_DESCRIPTION("Wilco EC telemetry driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:" DRV_NAME);
|
29
drivers/platform/olpc/Kconfig
Arquivo normal
29
drivers/platform/olpc/Kconfig
Arquivo normal
@@ -0,0 +1,29 @@
|
||||
config OLPC_EC
|
||||
select REGULATOR
|
||||
bool
|
||||
|
||||
menuconfig OLPC_XO175
|
||||
bool "Platform support for OLPC XO 1.75 hardware"
|
||||
depends on ARCH_MMP || COMPILE_TEST
|
||||
help
|
||||
Say Y here to get to see options for the ARM-based OLPC platform.
|
||||
This option alone does not add any kernel code.
|
||||
|
||||
Unless you have an OLPC XO laptop, you will want to say N.
|
||||
|
||||
if OLPC_XO175
|
||||
|
||||
config OLPC_XO175_EC
|
||||
tristate "OLPC XO 1.75 Embedded Controller"
|
||||
depends on SPI_SLAVE
|
||||
depends on INPUT
|
||||
depends on POWER_SUPPLY
|
||||
select OLPC_EC
|
||||
help
|
||||
Include support for the OLPC XO Embedded Controller (EC). The EC
|
||||
provides various platform services, including support for the power,
|
||||
button, restart, shutdown and battery charging status.
|
||||
|
||||
Unless you have an OLPC XO laptop, you will want to say N.
|
||||
|
||||
endif # OLPC_XO175
|
@@ -2,4 +2,5 @@
|
||||
#
|
||||
# OLPC XO platform-specific drivers
|
||||
#
|
||||
obj-$(CONFIG_OLPC) += olpc-ec.o
|
||||
obj-$(CONFIG_OLPC_EC) += olpc-ec.o
|
||||
obj-$(CONFIG_OLPC_XO175_EC) += olpc-xo175-ec.o
|
||||
|
@@ -15,8 +15,8 @@
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/regulator/driver.h>
|
||||
#include <linux/olpc-ec.h>
|
||||
#include <asm/olpc.h>
|
||||
|
||||
struct ec_cmd_desc {
|
||||
u8 cmd;
|
||||
@@ -32,15 +32,26 @@ struct ec_cmd_desc {
|
||||
|
||||
struct olpc_ec_priv {
|
||||
struct olpc_ec_driver *drv;
|
||||
u8 version;
|
||||
struct work_struct worker;
|
||||
struct mutex cmd_lock;
|
||||
|
||||
/* DCON regulator */
|
||||
struct regulator_dev *dcon_rdev;
|
||||
bool dcon_enabled;
|
||||
|
||||
/* Pending EC commands */
|
||||
struct list_head cmd_q;
|
||||
spinlock_t cmd_q_lock;
|
||||
|
||||
struct dentry *dbgfs_dir;
|
||||
|
||||
/*
|
||||
* EC event mask to be applied during suspend (defining wakeup
|
||||
* sources).
|
||||
*/
|
||||
u16 ec_wakeup_mask;
|
||||
|
||||
/*
|
||||
* Running an EC command while suspending means we don't always finish
|
||||
* the command before the machine suspends. This means that the EC
|
||||
@@ -118,8 +129,11 @@ int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, size_t outlen)
|
||||
struct olpc_ec_priv *ec = ec_priv;
|
||||
struct ec_cmd_desc desc;
|
||||
|
||||
/* Ensure a driver and ec hook have been registered */
|
||||
if (WARN_ON(!ec_driver || !ec_driver->ec_cmd))
|
||||
/* Driver not yet registered. */
|
||||
if (!ec_driver)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
if (WARN_ON(!ec_driver->ec_cmd))
|
||||
return -ENODEV;
|
||||
|
||||
if (!ec)
|
||||
@@ -149,6 +163,88 @@ int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, size_t outlen)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(olpc_ec_cmd);
|
||||
|
||||
void olpc_ec_wakeup_set(u16 value)
|
||||
{
|
||||
struct olpc_ec_priv *ec = ec_priv;
|
||||
|
||||
if (WARN_ON(!ec))
|
||||
return;
|
||||
|
||||
ec->ec_wakeup_mask |= value;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(olpc_ec_wakeup_set);
|
||||
|
||||
void olpc_ec_wakeup_clear(u16 value)
|
||||
{
|
||||
struct olpc_ec_priv *ec = ec_priv;
|
||||
|
||||
if (WARN_ON(!ec))
|
||||
return;
|
||||
|
||||
ec->ec_wakeup_mask &= ~value;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(olpc_ec_wakeup_clear);
|
||||
|
||||
int olpc_ec_mask_write(u16 bits)
|
||||
{
|
||||
struct olpc_ec_priv *ec = ec_priv;
|
||||
|
||||
if (WARN_ON(!ec))
|
||||
return -ENODEV;
|
||||
|
||||
/* EC version 0x5f adds support for wide SCI mask */
|
||||
if (ec->version >= 0x5f) {
|
||||
__be16 ec_word = cpu_to_be16(bits);
|
||||
|
||||
return olpc_ec_cmd(EC_WRITE_EXT_SCI_MASK, (void *)&ec_word, 2, NULL, 0);
|
||||
} else {
|
||||
u8 ec_byte = bits & 0xff;
|
||||
|
||||
return olpc_ec_cmd(EC_WRITE_SCI_MASK, &ec_byte, 1, NULL, 0);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(olpc_ec_mask_write);
|
||||
|
||||
/*
|
||||
* Returns true if the compile and runtime configurations allow for EC events
|
||||
* to wake the system.
|
||||
*/
|
||||
bool olpc_ec_wakeup_available(void)
|
||||
{
|
||||
if (WARN_ON(!ec_driver))
|
||||
return false;
|
||||
|
||||
return ec_driver->wakeup_available;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(olpc_ec_wakeup_available);
|
||||
|
||||
int olpc_ec_sci_query(u16 *sci_value)
|
||||
{
|
||||
struct olpc_ec_priv *ec = ec_priv;
|
||||
int ret;
|
||||
|
||||
if (WARN_ON(!ec))
|
||||
return -ENODEV;
|
||||
|
||||
/* EC version 0x5f adds support for wide SCI mask */
|
||||
if (ec->version >= 0x5f) {
|
||||
__be16 ec_word;
|
||||
|
||||
ret = olpc_ec_cmd(EC_EXT_SCI_QUERY, NULL, 0, (void *)&ec_word, 2);
|
||||
if (ret == 0)
|
||||
*sci_value = be16_to_cpu(ec_word);
|
||||
} else {
|
||||
u8 ec_byte;
|
||||
|
||||
ret = olpc_ec_cmd(EC_SCI_QUERY, NULL, 0, &ec_byte, 1);
|
||||
if (ret == 0)
|
||||
*sci_value = ec_byte;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(olpc_ec_sci_query);
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
|
||||
/*
|
||||
@@ -254,9 +350,61 @@ static struct dentry *olpc_ec_setup_debugfs(void)
|
||||
|
||||
#endif /* CONFIG_DEBUG_FS */
|
||||
|
||||
static int olpc_ec_set_dcon_power(struct olpc_ec_priv *ec, bool state)
|
||||
{
|
||||
unsigned char ec_byte = state;
|
||||
int ret;
|
||||
|
||||
if (ec->dcon_enabled == state)
|
||||
return 0;
|
||||
|
||||
ret = olpc_ec_cmd(EC_DCON_POWER_MODE, &ec_byte, 1, NULL, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ec->dcon_enabled = state;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dcon_regulator_enable(struct regulator_dev *rdev)
|
||||
{
|
||||
struct olpc_ec_priv *ec = rdev_get_drvdata(rdev);
|
||||
|
||||
return olpc_ec_set_dcon_power(ec, true);
|
||||
}
|
||||
|
||||
static int dcon_regulator_disable(struct regulator_dev *rdev)
|
||||
{
|
||||
struct olpc_ec_priv *ec = rdev_get_drvdata(rdev);
|
||||
|
||||
return olpc_ec_set_dcon_power(ec, false);
|
||||
}
|
||||
|
||||
static int dcon_regulator_is_enabled(struct regulator_dev *rdev)
|
||||
{
|
||||
struct olpc_ec_priv *ec = rdev_get_drvdata(rdev);
|
||||
|
||||
return ec->dcon_enabled ? 1 : 0;
|
||||
}
|
||||
|
||||
static struct regulator_ops dcon_regulator_ops = {
|
||||
.enable = dcon_regulator_enable,
|
||||
.disable = dcon_regulator_disable,
|
||||
.is_enabled = dcon_regulator_is_enabled,
|
||||
};
|
||||
|
||||
static const struct regulator_desc dcon_desc = {
|
||||
.name = "dcon",
|
||||
.id = 0,
|
||||
.ops = &dcon_regulator_ops,
|
||||
.type = REGULATOR_VOLTAGE,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int olpc_ec_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct olpc_ec_priv *ec;
|
||||
struct regulator_config config = { };
|
||||
int err;
|
||||
|
||||
if (!ec_driver)
|
||||
@@ -276,14 +424,26 @@ static int olpc_ec_probe(struct platform_device *pdev)
|
||||
ec_priv = ec;
|
||||
platform_set_drvdata(pdev, ec);
|
||||
|
||||
err = ec_driver->probe ? ec_driver->probe(pdev) : 0;
|
||||
/* get the EC revision */
|
||||
err = olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0, &ec->version, 1);
|
||||
if (err) {
|
||||
ec_priv = NULL;
|
||||
kfree(ec);
|
||||
} else {
|
||||
ec->dbgfs_dir = olpc_ec_setup_debugfs();
|
||||
return err;
|
||||
}
|
||||
|
||||
config.dev = pdev->dev.parent;
|
||||
config.driver_data = ec;
|
||||
ec->dcon_enabled = true;
|
||||
ec->dcon_rdev = devm_regulator_register(&pdev->dev, &dcon_desc,
|
||||
&config);
|
||||
if (IS_ERR(ec->dcon_rdev)) {
|
||||
dev_err(&pdev->dev, "failed to register DCON regulator\n");
|
||||
return PTR_ERR(ec->dcon_rdev);
|
||||
}
|
||||
|
||||
ec->dbgfs_dir = olpc_ec_setup_debugfs();
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -293,6 +453,8 @@ static int olpc_ec_suspend(struct device *dev)
|
||||
struct olpc_ec_priv *ec = platform_get_drvdata(pdev);
|
||||
int err = 0;
|
||||
|
||||
olpc_ec_mask_write(ec->ec_wakeup_mask);
|
||||
|
||||
if (ec_driver->suspend)
|
||||
err = ec_driver->suspend(pdev);
|
||||
if (!err)
|
||||
|
759
drivers/platform/olpc/olpc-xo175-ec.c
Arquivo normal
759
drivers/platform/olpc/olpc-xo175-ec.c
Arquivo normal
@@ -0,0 +1,759 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Driver for the OLPC XO-1.75 Embedded Controller.
|
||||
*
|
||||
* The EC protocol is documented at:
|
||||
* http://wiki.laptop.org/go/XO_1.75_HOST_to_EC_Protocol
|
||||
*
|
||||
* Copyright (C) 2010 One Laptop per Child Foundation.
|
||||
* Copyright (C) 2018 Lubomir Rintel <lkundrak@v3.sk>
|
||||
*/
|
||||
|
||||
#include <linux/completion.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/kfifo.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/olpc-ec.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
struct ec_cmd_t {
|
||||
u8 cmd;
|
||||
u8 bytes_returned;
|
||||
};
|
||||
|
||||
enum ec_chan_t {
|
||||
CHAN_NONE = 0,
|
||||
CHAN_SWITCH,
|
||||
CHAN_CMD_RESP,
|
||||
CHAN_KEYBOARD,
|
||||
CHAN_TOUCHPAD,
|
||||
CHAN_EVENT,
|
||||
CHAN_DEBUG,
|
||||
CHAN_CMD_ERROR,
|
||||
};
|
||||
|
||||
/*
|
||||
* EC events
|
||||
*/
|
||||
#define EVENT_AC_CHANGE 1 /* AC plugged/unplugged */
|
||||
#define EVENT_BATTERY_STATUS 2 /* Battery low/full/error/gone */
|
||||
#define EVENT_BATTERY_CRITICAL 3 /* Battery critical voltage */
|
||||
#define EVENT_BATTERY_SOC_CHANGE 4 /* 1% SOC Change */
|
||||
#define EVENT_BATTERY_ERROR 5 /* Abnormal error, query for cause */
|
||||
#define EVENT_POWER_PRESSED 6 /* Power button was pressed */
|
||||
#define EVENT_POWER_PRESS_WAKE 7 /* Woken up with a power button */
|
||||
#define EVENT_TIMED_HOST_WAKE 8 /* Host wake timer */
|
||||
#define EVENT_OLS_HIGH_LIMIT 9 /* OLS crossed dark threshold */
|
||||
#define EVENT_OLS_LOW_LIMIT 10 /* OLS crossed light threshold */
|
||||
|
||||
/*
|
||||
* EC commands
|
||||
* (from http://dev.laptop.org/git/users/rsmith/ec-1.75/tree/ec_cmd.h)
|
||||
*/
|
||||
#define CMD_GET_API_VERSION 0x08 /* out: u8 */
|
||||
#define CMD_READ_VOLTAGE 0x10 /* out: u16, *9.76/32, mV */
|
||||
#define CMD_READ_CURRENT 0x11 /* out: s16, *15.625/120, mA */
|
||||
#define CMD_READ_ACR 0x12 /* out: s16, *6250/15, uAh */
|
||||
#define CMD_READ_BATT_TEMPERATURE 0x13 /* out: u16, *100/256, deg C */
|
||||
#define CMD_READ_AMBIENT_TEMPERATURE 0x14 /* unimplemented, no hardware */
|
||||
#define CMD_READ_BATTERY_STATUS 0x15 /* out: u8, bitmask */
|
||||
#define CMD_READ_SOC 0x16 /* out: u8, percentage */
|
||||
#define CMD_READ_GAUGE_ID 0x17 /* out: u8 * 8 */
|
||||
#define CMD_READ_GAUGE_DATA 0x18 /* in: u8 addr, out: u8 data */
|
||||
#define CMD_READ_BOARD_ID 0x19 /* out: u16 (platform id) */
|
||||
#define CMD_READ_BATT_ERR_CODE 0x1f /* out: u8, error bitmask */
|
||||
#define CMD_SET_DCON_POWER 0x26 /* in: u8 */
|
||||
#define CMD_RESET_EC 0x28 /* none */
|
||||
#define CMD_READ_BATTERY_TYPE 0x2c /* out: u8 */
|
||||
#define CMD_SET_AUTOWAK 0x33 /* out: u8 */
|
||||
#define CMD_SET_EC_WAKEUP_TIMER 0x36 /* in: u32, out: ? */
|
||||
#define CMD_READ_EXT_SCI_MASK 0x37 /* ? */
|
||||
#define CMD_WRITE_EXT_SCI_MASK 0x38 /* ? */
|
||||
#define CMD_CLEAR_EC_WAKEUP_TIMER 0x39 /* none */
|
||||
#define CMD_ENABLE_RUNIN_DISCHARGE 0x3B /* none */
|
||||
#define CMD_DISABLE_RUNIN_DISCHARGE 0x3C /* none */
|
||||
#define CMD_READ_MPPT_ACTIVE 0x3d /* out: u8 */
|
||||
#define CMD_READ_MPPT_LIMIT 0x3e /* out: u8 */
|
||||
#define CMD_SET_MPPT_LIMIT 0x3f /* in: u8 */
|
||||
#define CMD_DISABLE_MPPT 0x40 /* none */
|
||||
#define CMD_ENABLE_MPPT 0x41 /* none */
|
||||
#define CMD_READ_VIN 0x42 /* out: u16 */
|
||||
#define CMD_EXT_SCI_QUERY 0x43 /* ? */
|
||||
#define RSP_KEYBOARD_DATA 0x48 /* ? */
|
||||
#define RSP_TOUCHPAD_DATA 0x49 /* ? */
|
||||
#define CMD_GET_FW_VERSION 0x4a /* out: u8 * 16 */
|
||||
#define CMD_POWER_CYCLE 0x4b /* none */
|
||||
#define CMD_POWER_OFF 0x4c /* none */
|
||||
#define CMD_RESET_EC_SOFT 0x4d /* none */
|
||||
#define CMD_READ_GAUGE_U16 0x4e /* ? */
|
||||
#define CMD_ENABLE_MOUSE 0x4f /* ? */
|
||||
#define CMD_ECHO 0x52 /* in: u8 * 5, out: u8 * 5 */
|
||||
#define CMD_GET_FW_DATE 0x53 /* out: u8 * 16 */
|
||||
#define CMD_GET_FW_USER 0x54 /* out: u8 * 16 */
|
||||
#define CMD_TURN_OFF_POWER 0x55 /* none (same as 0x4c) */
|
||||
#define CMD_READ_OLS 0x56 /* out: u16 */
|
||||
#define CMD_OLS_SMT_LEDON 0x57 /* none */
|
||||
#define CMD_OLS_SMT_LEDOFF 0x58 /* none */
|
||||
#define CMD_START_OLS_ASSY 0x59 /* none */
|
||||
#define CMD_STOP_OLS_ASSY 0x5a /* none */
|
||||
#define CMD_OLS_SMTTEST_STOP 0x5b /* none */
|
||||
#define CMD_READ_VIN_SCALED 0x5c /* out: u16 */
|
||||
#define CMD_READ_BAT_MIN_W 0x5d /* out: u16 */
|
||||
#define CMD_READ_BAR_MAX_W 0x5e /* out: u16 */
|
||||
#define CMD_RESET_BAT_MINMAX_W 0x5f /* none */
|
||||
#define CMD_READ_LOCATION 0x60 /* in: u16 addr, out: u8 data */
|
||||
#define CMD_WRITE_LOCATION 0x61 /* in: u16 addr, u8 data */
|
||||
#define CMD_KEYBOARD_CMD 0x62 /* in: u8, out: ? */
|
||||
#define CMD_TOUCHPAD_CMD 0x63 /* in: u8, out: ? */
|
||||
#define CMD_GET_FW_HASH 0x64 /* out: u8 * 16 */
|
||||
#define CMD_SUSPEND_HINT 0x65 /* in: u8 */
|
||||
#define CMD_ENABLE_WAKE_TIMER 0x66 /* in: u8 */
|
||||
#define CMD_SET_WAKE_TIMER 0x67 /* in: 32 */
|
||||
#define CMD_ENABLE_WAKE_AUTORESET 0x68 /* in: u8 */
|
||||
#define CMD_OLS_SET_LIMITS 0x69 /* in: u16, u16 */
|
||||
#define CMD_OLS_GET_LIMITS 0x6a /* out: u16, u16 */
|
||||
#define CMD_OLS_SET_CEILING 0x6b /* in: u16 */
|
||||
#define CMD_OLS_GET_CEILING 0x6c /* out: u16 */
|
||||
|
||||
/*
|
||||
* Accepted EC commands, and how many bytes they return. There are plenty
|
||||
* of EC commands that are no longer implemented, or are implemented only on
|
||||
* certain older boards.
|
||||
*/
|
||||
static const struct ec_cmd_t olpc_xo175_ec_cmds[] = {
|
||||
{ CMD_GET_API_VERSION, 1 },
|
||||
{ CMD_READ_VOLTAGE, 2 },
|
||||
{ CMD_READ_CURRENT, 2 },
|
||||
{ CMD_READ_ACR, 2 },
|
||||
{ CMD_READ_BATT_TEMPERATURE, 2 },
|
||||
{ CMD_READ_BATTERY_STATUS, 1 },
|
||||
{ CMD_READ_SOC, 1 },
|
||||
{ CMD_READ_GAUGE_ID, 8 },
|
||||
{ CMD_READ_GAUGE_DATA, 1 },
|
||||
{ CMD_READ_BOARD_ID, 2 },
|
||||
{ CMD_READ_BATT_ERR_CODE, 1 },
|
||||
{ CMD_SET_DCON_POWER, 0 },
|
||||
{ CMD_RESET_EC, 0 },
|
||||
{ CMD_READ_BATTERY_TYPE, 1 },
|
||||
{ CMD_ENABLE_RUNIN_DISCHARGE, 0 },
|
||||
{ CMD_DISABLE_RUNIN_DISCHARGE, 0 },
|
||||
{ CMD_READ_MPPT_ACTIVE, 1 },
|
||||
{ CMD_READ_MPPT_LIMIT, 1 },
|
||||
{ CMD_SET_MPPT_LIMIT, 0 },
|
||||
{ CMD_DISABLE_MPPT, 0 },
|
||||
{ CMD_ENABLE_MPPT, 0 },
|
||||
{ CMD_READ_VIN, 2 },
|
||||
{ CMD_GET_FW_VERSION, 16 },
|
||||
{ CMD_POWER_CYCLE, 0 },
|
||||
{ CMD_POWER_OFF, 0 },
|
||||
{ CMD_RESET_EC_SOFT, 0 },
|
||||
{ CMD_ECHO, 5 },
|
||||
{ CMD_GET_FW_DATE, 16 },
|
||||
{ CMD_GET_FW_USER, 16 },
|
||||
{ CMD_TURN_OFF_POWER, 0 },
|
||||
{ CMD_READ_OLS, 2 },
|
||||
{ CMD_OLS_SMT_LEDON, 0 },
|
||||
{ CMD_OLS_SMT_LEDOFF, 0 },
|
||||
{ CMD_START_OLS_ASSY, 0 },
|
||||
{ CMD_STOP_OLS_ASSY, 0 },
|
||||
{ CMD_OLS_SMTTEST_STOP, 0 },
|
||||
{ CMD_READ_VIN_SCALED, 2 },
|
||||
{ CMD_READ_BAT_MIN_W, 2 },
|
||||
{ CMD_READ_BAR_MAX_W, 2 },
|
||||
{ CMD_RESET_BAT_MINMAX_W, 0 },
|
||||
{ CMD_READ_LOCATION, 1 },
|
||||
{ CMD_WRITE_LOCATION, 0 },
|
||||
{ CMD_GET_FW_HASH, 16 },
|
||||
{ CMD_SUSPEND_HINT, 0 },
|
||||
{ CMD_ENABLE_WAKE_TIMER, 0 },
|
||||
{ CMD_SET_WAKE_TIMER, 0 },
|
||||
{ CMD_ENABLE_WAKE_AUTORESET, 0 },
|
||||
{ CMD_OLS_SET_LIMITS, 0 },
|
||||
{ CMD_OLS_GET_LIMITS, 4 },
|
||||
{ CMD_OLS_SET_CEILING, 0 },
|
||||
{ CMD_OLS_GET_CEILING, 2 },
|
||||
{ CMD_READ_EXT_SCI_MASK, 2 },
|
||||
{ CMD_WRITE_EXT_SCI_MASK, 0 },
|
||||
|
||||
{ }
|
||||
};
|
||||
|
||||
#define EC_MAX_CMD_DATA_LEN 5
|
||||
#define EC_MAX_RESP_LEN 16
|
||||
|
||||
#define LOG_BUF_SIZE 128
|
||||
|
||||
#define PM_WAKEUP_TIME 1000
|
||||
|
||||
#define EC_ALL_EVENTS GENMASK(15, 0)
|
||||
|
||||
enum ec_state_t {
|
||||
CMD_STATE_IDLE = 0,
|
||||
CMD_STATE_WAITING_FOR_SWITCH,
|
||||
CMD_STATE_CMD_IN_TX_FIFO,
|
||||
CMD_STATE_CMD_SENT,
|
||||
CMD_STATE_RESP_RECEIVED,
|
||||
CMD_STATE_ERROR_RECEIVED,
|
||||
};
|
||||
|
||||
struct olpc_xo175_ec_cmd {
|
||||
u8 command;
|
||||
u8 nr_args;
|
||||
u8 data_len;
|
||||
u8 args[EC_MAX_CMD_DATA_LEN];
|
||||
};
|
||||
|
||||
struct olpc_xo175_ec_resp {
|
||||
u8 channel;
|
||||
u8 byte;
|
||||
};
|
||||
|
||||
struct olpc_xo175_ec {
|
||||
bool suspended;
|
||||
|
||||
/* SPI related stuff. */
|
||||
struct spi_device *spi;
|
||||
struct spi_transfer xfer;
|
||||
struct spi_message msg;
|
||||
union {
|
||||
struct olpc_xo175_ec_cmd cmd;
|
||||
struct olpc_xo175_ec_resp resp;
|
||||
} tx_buf, rx_buf;
|
||||
|
||||
/* GPIO for the CMD signals. */
|
||||
struct gpio_desc *gpio_cmd;
|
||||
|
||||
/* Command handling related state. */
|
||||
spinlock_t cmd_state_lock;
|
||||
int cmd_state;
|
||||
bool cmd_running;
|
||||
struct completion cmd_done;
|
||||
struct olpc_xo175_ec_cmd cmd;
|
||||
u8 resp_data[EC_MAX_RESP_LEN];
|
||||
int expected_resp_len;
|
||||
int resp_len;
|
||||
|
||||
/* Power button. */
|
||||
struct input_dev *pwrbtn;
|
||||
|
||||
/* Debug handling. */
|
||||
char logbuf[LOG_BUF_SIZE];
|
||||
int logbuf_len;
|
||||
};
|
||||
|
||||
static struct platform_device *olpc_ec;
|
||||
|
||||
static int olpc_xo175_ec_resp_len(u8 cmd)
|
||||
{
|
||||
const struct ec_cmd_t *p;
|
||||
|
||||
for (p = olpc_xo175_ec_cmds; p->cmd; p++) {
|
||||
if (p->cmd == cmd)
|
||||
return p->bytes_returned;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void olpc_xo175_ec_flush_logbuf(struct olpc_xo175_ec *priv)
|
||||
{
|
||||
dev_dbg(&priv->spi->dev, "got debug string [%*pE]\n",
|
||||
priv->logbuf_len, priv->logbuf);
|
||||
priv->logbuf_len = 0;
|
||||
}
|
||||
|
||||
static void olpc_xo175_ec_complete(void *arg);
|
||||
|
||||
static void olpc_xo175_ec_send_command(struct olpc_xo175_ec *priv, void *cmd,
|
||||
size_t cmdlen)
|
||||
{
|
||||
int ret;
|
||||
|
||||
memcpy(&priv->tx_buf, cmd, cmdlen);
|
||||
priv->xfer.len = cmdlen;
|
||||
|
||||
spi_message_init_with_transfers(&priv->msg, &priv->xfer, 1);
|
||||
|
||||
priv->msg.complete = olpc_xo175_ec_complete;
|
||||
priv->msg.context = priv;
|
||||
|
||||
ret = spi_async(priv->spi, &priv->msg);
|
||||
if (ret)
|
||||
dev_err(&priv->spi->dev, "spi_async() failed %d\n", ret);
|
||||
}
|
||||
|
||||
static void olpc_xo175_ec_read_packet(struct olpc_xo175_ec *priv)
|
||||
{
|
||||
u8 nonce[] = {0xA5, 0x5A};
|
||||
|
||||
olpc_xo175_ec_send_command(priv, nonce, sizeof(nonce));
|
||||
}
|
||||
|
||||
static void olpc_xo175_ec_complete(void *arg)
|
||||
{
|
||||
struct olpc_xo175_ec *priv = arg;
|
||||
struct device *dev = &priv->spi->dev;
|
||||
struct power_supply *psy;
|
||||
unsigned long flags;
|
||||
u8 channel;
|
||||
u8 byte;
|
||||
int ret;
|
||||
|
||||
ret = priv->msg.status;
|
||||
if (ret) {
|
||||
dev_err(dev, "SPI transfer failed: %d\n", ret);
|
||||
|
||||
spin_lock_irqsave(&priv->cmd_state_lock, flags);
|
||||
if (priv->cmd_running) {
|
||||
priv->resp_len = 0;
|
||||
priv->cmd_state = CMD_STATE_ERROR_RECEIVED;
|
||||
complete(&priv->cmd_done);
|
||||
}
|
||||
spin_unlock_irqrestore(&priv->cmd_state_lock, flags);
|
||||
|
||||
if (ret != -EINTR)
|
||||
olpc_xo175_ec_read_packet(priv);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
channel = priv->rx_buf.resp.channel;
|
||||
byte = priv->rx_buf.resp.byte;
|
||||
|
||||
switch (channel) {
|
||||
case CHAN_NONE:
|
||||
spin_lock_irqsave(&priv->cmd_state_lock, flags);
|
||||
|
||||
if (!priv->cmd_running) {
|
||||
/* We can safely ignore these */
|
||||
dev_err(dev, "spurious FIFO read packet\n");
|
||||
spin_unlock_irqrestore(&priv->cmd_state_lock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
priv->cmd_state = CMD_STATE_CMD_SENT;
|
||||
if (!priv->expected_resp_len)
|
||||
complete(&priv->cmd_done);
|
||||
olpc_xo175_ec_read_packet(priv);
|
||||
|
||||
spin_unlock_irqrestore(&priv->cmd_state_lock, flags);
|
||||
return;
|
||||
|
||||
case CHAN_SWITCH:
|
||||
spin_lock_irqsave(&priv->cmd_state_lock, flags);
|
||||
|
||||
if (!priv->cmd_running) {
|
||||
/* Just go with the flow */
|
||||
dev_err(dev, "spurious SWITCH packet\n");
|
||||
memset(&priv->cmd, 0, sizeof(priv->cmd));
|
||||
priv->cmd.command = CMD_ECHO;
|
||||
}
|
||||
|
||||
priv->cmd_state = CMD_STATE_CMD_IN_TX_FIFO;
|
||||
|
||||
/* Throw command into TxFIFO */
|
||||
gpiod_set_value_cansleep(priv->gpio_cmd, 0);
|
||||
olpc_xo175_ec_send_command(priv, &priv->cmd, sizeof(priv->cmd));
|
||||
|
||||
spin_unlock_irqrestore(&priv->cmd_state_lock, flags);
|
||||
return;
|
||||
|
||||
case CHAN_CMD_RESP:
|
||||
spin_lock_irqsave(&priv->cmd_state_lock, flags);
|
||||
|
||||
if (!priv->cmd_running) {
|
||||
dev_err(dev, "spurious response packet\n");
|
||||
} else if (priv->resp_len >= priv->expected_resp_len) {
|
||||
dev_err(dev, "too many response packets\n");
|
||||
} else {
|
||||
priv->resp_data[priv->resp_len++] = byte;
|
||||
if (priv->resp_len == priv->expected_resp_len) {
|
||||
priv->cmd_state = CMD_STATE_RESP_RECEIVED;
|
||||
complete(&priv->cmd_done);
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&priv->cmd_state_lock, flags);
|
||||
break;
|
||||
|
||||
case CHAN_CMD_ERROR:
|
||||
spin_lock_irqsave(&priv->cmd_state_lock, flags);
|
||||
|
||||
if (!priv->cmd_running) {
|
||||
dev_err(dev, "spurious cmd error packet\n");
|
||||
} else {
|
||||
priv->resp_data[0] = byte;
|
||||
priv->resp_len = 1;
|
||||
priv->cmd_state = CMD_STATE_ERROR_RECEIVED;
|
||||
complete(&priv->cmd_done);
|
||||
}
|
||||
spin_unlock_irqrestore(&priv->cmd_state_lock, flags);
|
||||
break;
|
||||
|
||||
case CHAN_KEYBOARD:
|
||||
dev_warn(dev, "keyboard is not supported\n");
|
||||
break;
|
||||
|
||||
case CHAN_TOUCHPAD:
|
||||
dev_warn(dev, "touchpad is not supported\n");
|
||||
break;
|
||||
|
||||
case CHAN_EVENT:
|
||||
dev_dbg(dev, "got event %.2x\n", byte);
|
||||
switch (byte) {
|
||||
case EVENT_AC_CHANGE:
|
||||
psy = power_supply_get_by_name("olpc-ac");
|
||||
if (psy) {
|
||||
power_supply_changed(psy);
|
||||
power_supply_put(psy);
|
||||
}
|
||||
break;
|
||||
case EVENT_BATTERY_STATUS:
|
||||
case EVENT_BATTERY_CRITICAL:
|
||||
case EVENT_BATTERY_SOC_CHANGE:
|
||||
case EVENT_BATTERY_ERROR:
|
||||
psy = power_supply_get_by_name("olpc-battery");
|
||||
if (psy) {
|
||||
power_supply_changed(psy);
|
||||
power_supply_put(psy);
|
||||
}
|
||||
break;
|
||||
case EVENT_POWER_PRESSED:
|
||||
input_report_key(priv->pwrbtn, KEY_POWER, 1);
|
||||
input_sync(priv->pwrbtn);
|
||||
input_report_key(priv->pwrbtn, KEY_POWER, 0);
|
||||
input_sync(priv->pwrbtn);
|
||||
/* fall through */
|
||||
case EVENT_POWER_PRESS_WAKE:
|
||||
case EVENT_TIMED_HOST_WAKE:
|
||||
pm_wakeup_event(priv->pwrbtn->dev.parent,
|
||||
PM_WAKEUP_TIME);
|
||||
break;
|
||||
default:
|
||||
dev_dbg(dev, "ignored unknown event %.2x\n", byte);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case CHAN_DEBUG:
|
||||
if (byte == '\n') {
|
||||
olpc_xo175_ec_flush_logbuf(priv);
|
||||
} else if (isprint(byte)) {
|
||||
priv->logbuf[priv->logbuf_len++] = byte;
|
||||
if (priv->logbuf_len == LOG_BUF_SIZE)
|
||||
olpc_xo175_ec_flush_logbuf(priv);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_warn(dev, "unknown channel: %d, %.2x\n", channel, byte);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Most non-command packets get the TxFIFO refilled and an ACK. */
|
||||
olpc_xo175_ec_read_packet(priv);
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is protected with a mutex. We can safely assume that
|
||||
* there will be only one instance of this function running at a time.
|
||||
* One of the ways in which we enforce this is by waiting until we get
|
||||
* all response bytes back from the EC, rather than just the number that
|
||||
* the caller requests (otherwise, we might start a new command while an
|
||||
* old command's response bytes are still incoming).
|
||||
*/
|
||||
static int olpc_xo175_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *resp,
|
||||
size_t resp_len, void *ec_cb_arg)
|
||||
{
|
||||
struct olpc_xo175_ec *priv = ec_cb_arg;
|
||||
struct device *dev = &priv->spi->dev;
|
||||
unsigned long flags;
|
||||
size_t nr_bytes;
|
||||
int ret = 0;
|
||||
|
||||
dev_dbg(dev, "CMD %x, %zd bytes expected\n", cmd, resp_len);
|
||||
|
||||
if (inlen > 5) {
|
||||
dev_err(dev, "command len %zd too big!\n", resp_len);
|
||||
return -EOVERFLOW;
|
||||
}
|
||||
|
||||
/* Suspending in the middle of an EC command hoses things badly! */
|
||||
if (WARN_ON(priv->suspended))
|
||||
return -EBUSY;
|
||||
|
||||
/* Ensure a valid command and return bytes */
|
||||
ret = olpc_xo175_ec_resp_len(cmd);
|
||||
if (ret < 0) {
|
||||
dev_err_ratelimited(dev, "unknown command 0x%x\n", cmd);
|
||||
|
||||
/*
|
||||
* Assume the best in our callers, and allow unknown commands
|
||||
* through. I'm not the charitable type, but it was beaten
|
||||
* into me. Just maintain a minimum standard of sanity.
|
||||
*/
|
||||
if (resp_len > sizeof(priv->resp_data)) {
|
||||
dev_err(dev, "response too big: %zd!\n", resp_len);
|
||||
return -EOVERFLOW;
|
||||
}
|
||||
nr_bytes = resp_len;
|
||||
} else {
|
||||
nr_bytes = (size_t)ret;
|
||||
ret = 0;
|
||||
}
|
||||
resp_len = min(resp_len, nr_bytes);
|
||||
|
||||
spin_lock_irqsave(&priv->cmd_state_lock, flags);
|
||||
|
||||
/* Initialize the state machine */
|
||||
init_completion(&priv->cmd_done);
|
||||
priv->cmd_running = true;
|
||||
priv->cmd_state = CMD_STATE_WAITING_FOR_SWITCH;
|
||||
memset(&priv->cmd, 0, sizeof(priv->cmd));
|
||||
priv->cmd.command = cmd;
|
||||
priv->cmd.nr_args = inlen;
|
||||
priv->cmd.data_len = 0;
|
||||
memcpy(priv->cmd.args, inbuf, inlen);
|
||||
priv->expected_resp_len = nr_bytes;
|
||||
priv->resp_len = 0;
|
||||
|
||||
/* Tickle the cmd gpio to get things started */
|
||||
gpiod_set_value_cansleep(priv->gpio_cmd, 1);
|
||||
|
||||
spin_unlock_irqrestore(&priv->cmd_state_lock, flags);
|
||||
|
||||
/* The irq handler should do the rest */
|
||||
if (!wait_for_completion_timeout(&priv->cmd_done,
|
||||
msecs_to_jiffies(4000))) {
|
||||
dev_err(dev, "EC cmd error: timeout in STATE %d\n",
|
||||
priv->cmd_state);
|
||||
gpiod_set_value_cansleep(priv->gpio_cmd, 0);
|
||||
spi_slave_abort(priv->spi);
|
||||
olpc_xo175_ec_read_packet(priv);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&priv->cmd_state_lock, flags);
|
||||
|
||||
/* Deal with the results. */
|
||||
if (priv->cmd_state == CMD_STATE_ERROR_RECEIVED) {
|
||||
/* EC-provided error is in the single response byte */
|
||||
dev_err(dev, "command 0x%x returned error 0x%x\n",
|
||||
cmd, priv->resp_data[0]);
|
||||
ret = -EREMOTEIO;
|
||||
} else if (priv->resp_len != nr_bytes) {
|
||||
dev_err(dev, "command 0x%x returned %d bytes, expected %zd bytes\n",
|
||||
cmd, priv->resp_len, nr_bytes);
|
||||
ret = -EREMOTEIO;
|
||||
} else {
|
||||
/*
|
||||
* We may have 8 bytes in priv->resp, but we only care about
|
||||
* what we've been asked for. If the caller asked for only 2
|
||||
* bytes, give them that. We've guaranteed that
|
||||
* resp_len <= priv->resp_len and priv->resp_len == nr_bytes.
|
||||
*/
|
||||
memcpy(resp, priv->resp_data, resp_len);
|
||||
}
|
||||
|
||||
/* This should already be low, but just in case. */
|
||||
gpiod_set_value_cansleep(priv->gpio_cmd, 0);
|
||||
priv->cmd_running = false;
|
||||
|
||||
spin_unlock_irqrestore(&priv->cmd_state_lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int olpc_xo175_ec_set_event_mask(unsigned int mask)
|
||||
{
|
||||
u8 args[2];
|
||||
|
||||
args[0] = mask >> 0;
|
||||
args[1] = mask >> 8;
|
||||
return olpc_ec_cmd(CMD_WRITE_EXT_SCI_MASK, args, 2, NULL, 0);
|
||||
}
|
||||
|
||||
static void olpc_xo175_ec_power_off(void)
|
||||
{
|
||||
while (1) {
|
||||
olpc_ec_cmd(CMD_POWER_OFF, NULL, 0, NULL, 0);
|
||||
mdelay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
static int __maybe_unused olpc_xo175_ec_suspend(struct device *dev)
|
||||
{
|
||||
struct olpc_xo175_ec *priv = dev_get_drvdata(dev);
|
||||
static struct {
|
||||
u8 suspend;
|
||||
u32 suspend_count;
|
||||
} __packed hintargs;
|
||||
static unsigned int suspend_count;
|
||||
|
||||
/*
|
||||
* SOC_SLEEP is not wired to the EC on B3 and earlier boards.
|
||||
* This command lets the EC know instead. The suspend count doesn't seem
|
||||
* to be used anywhere but in the EC debug output.
|
||||
*/
|
||||
hintargs.suspend = 1;
|
||||
hintargs.suspend_count = suspend_count++;
|
||||
olpc_ec_cmd(CMD_SUSPEND_HINT, (void *)&hintargs, sizeof(hintargs),
|
||||
NULL, 0);
|
||||
|
||||
/*
|
||||
* After we've sent the suspend hint, don't allow further EC commands
|
||||
* to be run until we've resumed. Userspace tasks should be frozen,
|
||||
* but kernel threads and interrupts could still schedule EC commands.
|
||||
*/
|
||||
priv->suspended = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused olpc_xo175_ec_resume_noirq(struct device *dev)
|
||||
{
|
||||
struct olpc_xo175_ec *priv = dev_get_drvdata(dev);
|
||||
|
||||
priv->suspended = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused olpc_xo175_ec_resume(struct device *dev)
|
||||
{
|
||||
u8 x = 0;
|
||||
|
||||
/*
|
||||
* The resume hint is only needed if no other commands are
|
||||
* being sent during resume. all it does is tell the EC
|
||||
* the SoC is definitely awake.
|
||||
*/
|
||||
olpc_ec_cmd(CMD_SUSPEND_HINT, &x, 1, NULL, 0);
|
||||
|
||||
/* Enable all EC events while we're awake */
|
||||
olpc_xo175_ec_set_event_mask(EC_ALL_EVENTS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct olpc_ec_driver olpc_xo175_ec_driver = {
|
||||
.ec_cmd = olpc_xo175_ec_cmd,
|
||||
};
|
||||
|
||||
static int olpc_xo175_ec_remove(struct spi_device *spi)
|
||||
{
|
||||
if (pm_power_off == olpc_xo175_ec_power_off)
|
||||
pm_power_off = NULL;
|
||||
|
||||
spi_slave_abort(spi);
|
||||
|
||||
platform_device_unregister(olpc_ec);
|
||||
olpc_ec = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int olpc_xo175_ec_probe(struct spi_device *spi)
|
||||
{
|
||||
struct olpc_xo175_ec *priv;
|
||||
int ret;
|
||||
|
||||
if (olpc_ec) {
|
||||
dev_err(&spi->dev, "OLPC EC already registered.\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->gpio_cmd = devm_gpiod_get(&spi->dev, "cmd", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(priv->gpio_cmd)) {
|
||||
dev_err(&spi->dev, "failed to get cmd gpio: %ld\n",
|
||||
PTR_ERR(priv->gpio_cmd));
|
||||
return PTR_ERR(priv->gpio_cmd);
|
||||
}
|
||||
|
||||
priv->spi = spi;
|
||||
|
||||
spin_lock_init(&priv->cmd_state_lock);
|
||||
priv->cmd_state = CMD_STATE_IDLE;
|
||||
init_completion(&priv->cmd_done);
|
||||
|
||||
priv->logbuf_len = 0;
|
||||
|
||||
/* Set up power button input device */
|
||||
priv->pwrbtn = devm_input_allocate_device(&spi->dev);
|
||||
if (!priv->pwrbtn)
|
||||
return -ENOMEM;
|
||||
priv->pwrbtn->name = "Power Button";
|
||||
priv->pwrbtn->dev.parent = &spi->dev;
|
||||
input_set_capability(priv->pwrbtn, EV_KEY, KEY_POWER);
|
||||
ret = input_register_device(priv->pwrbtn);
|
||||
if (ret) {
|
||||
dev_err(&spi->dev, "error registering input device: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
spi_set_drvdata(spi, priv);
|
||||
|
||||
priv->xfer.rx_buf = &priv->rx_buf;
|
||||
priv->xfer.tx_buf = &priv->tx_buf;
|
||||
|
||||
olpc_xo175_ec_read_packet(priv);
|
||||
|
||||
olpc_ec_driver_register(&olpc_xo175_ec_driver, priv);
|
||||
olpc_ec = platform_device_register_resndata(&spi->dev, "olpc-ec", -1,
|
||||
NULL, 0, NULL, 0);
|
||||
|
||||
/* Enable all EC events while we're awake */
|
||||
olpc_xo175_ec_set_event_mask(EC_ALL_EVENTS);
|
||||
|
||||
if (pm_power_off == NULL)
|
||||
pm_power_off = olpc_xo175_ec_power_off;
|
||||
|
||||
dev_info(&spi->dev, "OLPC XO-1.75 Embedded Controller driver\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops olpc_xo175_ec_pm_ops = {
|
||||
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, olpc_xo175_ec_resume_noirq)
|
||||
SET_RUNTIME_PM_OPS(olpc_xo175_ec_suspend, olpc_xo175_ec_resume, NULL)
|
||||
};
|
||||
|
||||
static const struct of_device_id olpc_xo175_ec_of_match[] = {
|
||||
{ .compatible = "olpc,xo1.75-ec" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, olpc_xo175_ec_of_match);
|
||||
|
||||
static const struct spi_device_id olpc_xo175_ec_id_table[] = {
|
||||
{ "xo1.75-ec", 0 },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, olpc_xo175_ec_id_table);
|
||||
|
||||
static struct spi_driver olpc_xo175_ec_spi_driver = {
|
||||
.driver = {
|
||||
.name = "olpc-xo175-ec",
|
||||
.of_match_table = olpc_xo175_ec_of_match,
|
||||
.pm = &olpc_xo175_ec_pm_ops,
|
||||
},
|
||||
.probe = olpc_xo175_ec_probe,
|
||||
.remove = olpc_xo175_ec_remove,
|
||||
};
|
||||
module_spi_driver(olpc_xo175_ec_spi_driver);
|
||||
|
||||
MODULE_DESCRIPTION("OLPC XO-1.75 Embedded Controller driver");
|
||||
MODULE_AUTHOR("Lennert Buytenhek <buytenh@wantstofly.org>"); /* Functionality */
|
||||
MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); /* Bugs */
|
||||
MODULE_LICENSE("GPL");
|
@@ -118,7 +118,7 @@ config DCDBAS
|
||||
Interrupts (SMIs) and Host Control Actions (system power cycle or
|
||||
power off after OS shutdown) on certain Dell systems.
|
||||
|
||||
See <file:Documentation/dcdbas.txt> for more details on the driver
|
||||
See <file:Documentation/driver-api/dcdbas.rst> for more details on the driver
|
||||
and the Dell systems on which Dell systems management software makes
|
||||
use of this driver.
|
||||
|
||||
@@ -259,7 +259,7 @@ config DELL_RBU
|
||||
DELL system. Note you need a Dell OpenManage or Dell Update package (DUP)
|
||||
supporting application to communicate with the BIOS regarding the new
|
||||
image for the image update to take effect.
|
||||
See <file:Documentation/dell_rbu.txt> for more details on the driver.
|
||||
See <file:Documentation/driver-api/dell_rbu.rst> for more details on the driver.
|
||||
|
||||
|
||||
config FUJITSU_LAPTOP
|
||||
@@ -341,7 +341,7 @@ config HP_ACCEL
|
||||
|
||||
Support for a led indicating disk protection will be provided as
|
||||
hp::hddprotect. For more information on the feature, refer to
|
||||
Documentation/misc-devices/lis3lv02d.
|
||||
Documentation/misc-devices/lis3lv02d.rst.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called hp_accel.
|
||||
@@ -433,9 +433,6 @@ config COMPAL_LAPTOP
|
||||
It adds support for rfkill, Bluetooth, WLAN, LCD brightness, hwmon
|
||||
and battery charging level control.
|
||||
|
||||
For a (possibly incomplete) list of supported laptops, please refer
|
||||
to: Documentation/platform/x86-laptop-drivers.txt
|
||||
|
||||
config SONY_LAPTOP
|
||||
tristate "Sony Laptop Extras"
|
||||
depends on ACPI
|
||||
@@ -451,7 +448,7 @@ config SONY_LAPTOP
|
||||
screen brightness control, Fn keys and allows powering on/off some
|
||||
devices.
|
||||
|
||||
Read <file:Documentation/laptops/sony-laptop.txt> for more information.
|
||||
Read <file:Documentation/admin-guide/laptops/sony-laptop.rst> for more information.
|
||||
|
||||
config SONYPI_COMPAT
|
||||
bool "Sonypi compatibility"
|
||||
@@ -503,7 +500,7 @@ config THINKPAD_ACPI
|
||||
support for Fn-Fx key combinations, Bluetooth control, video
|
||||
output switching, ThinkLight control, UltraBay eject and more.
|
||||
For more information about this driver see
|
||||
<file:Documentation/laptops/thinkpad-acpi.txt> and
|
||||
<file:Documentation/admin-guide/laptops/thinkpad-acpi.rst> and
|
||||
<http://ibm-acpi.sf.net/> .
|
||||
|
||||
This driver was formerly known as ibm-acpi.
|
||||
@@ -781,6 +778,16 @@ config INTEL_WMI_THUNDERBOLT
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called intel-wmi-thunderbolt.
|
||||
|
||||
config XIAOMI_WMI
|
||||
tristate "Xiaomi WMI key driver"
|
||||
depends on ACPI_WMI
|
||||
depends on INPUT
|
||||
help
|
||||
Say Y here if you want to support WMI-based keys on Xiaomi notebooks.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called xiaomi-wmi.
|
||||
|
||||
config MSI_WMI
|
||||
tristate "MSI WMI extras"
|
||||
depends on ACPI_WMI
|
||||
@@ -906,7 +913,6 @@ config TOSHIBA_WMI
|
||||
config ACPI_CMPC
|
||||
tristate "CMPC Laptop Extras"
|
||||
depends on ACPI && INPUT
|
||||
depends on BACKLIGHT_LCD_SUPPORT
|
||||
depends on RFKILL || RFKILL=n
|
||||
select BACKLIGHT_CLASS_DEVICE
|
||||
help
|
||||
@@ -1130,7 +1136,6 @@ config INTEL_OAKTRAIL
|
||||
config SAMSUNG_Q10
|
||||
tristate "Samsung Q10 Extras"
|
||||
depends on ACPI
|
||||
depends on BACKLIGHT_LCD_SUPPORT
|
||||
select BACKLIGHT_CLASS_DEVICE
|
||||
---help---
|
||||
This driver provides support for backlight control on Samsung Q10
|
||||
@@ -1317,7 +1322,7 @@ config HUAWEI_WMI
|
||||
|
||||
config PCENGINES_APU2
|
||||
tristate "PC Engines APUv2/3 front button and LEDs driver"
|
||||
depends on INPUT && INPUT_KEYBOARD
|
||||
depends on INPUT && INPUT_KEYBOARD && GPIOLIB
|
||||
depends on LEDS_CLASS
|
||||
select GPIO_AMD_FCH
|
||||
select KEYBOARD_GPIO_POLLED
|
||||
@@ -1329,6 +1334,8 @@ config PCENGINES_APU2
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called pcengines-apuv2.
|
||||
|
||||
source "drivers/platform/x86/intel_speed_select_if/Kconfig"
|
||||
|
||||
endif # X86_PLATFORM_DEVICES
|
||||
|
||||
config PMC_ATOM
|
||||
|
@@ -51,6 +51,7 @@ obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o
|
||||
obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o
|
||||
obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o
|
||||
obj-$(CONFIG_INTEL_WMI_THUNDERBOLT) += intel-wmi-thunderbolt.o
|
||||
obj-$(CONFIG_XIAOMI_WMI) += xiaomi-wmi.o
|
||||
|
||||
# toshiba_acpi must link after wmi to ensure that wmi devices are found
|
||||
# before toshiba_acpi initializes
|
||||
@@ -89,7 +90,7 @@ obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o
|
||||
obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \
|
||||
intel_telemetry_pltdrv.o \
|
||||
intel_telemetry_debugfs.o
|
||||
obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o
|
||||
obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o
|
||||
obj-$(CONFIG_PMC_ATOM) += pmc_atom.o
|
||||
obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o
|
||||
obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o
|
||||
@@ -98,3 +99,4 @@ obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o
|
||||
obj-$(CONFIG_I2C_MULTI_INSTANTIATE) += i2c-multi-instantiate.o
|
||||
obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o
|
||||
obj-$(CONFIG_PCENGINES_APU2) += pcengines-apuv2.o
|
||||
obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += intel_speed_select_if/
|
||||
|
@@ -259,7 +259,6 @@ struct acer_data {
|
||||
|
||||
struct acer_debug {
|
||||
struct dentry *root;
|
||||
struct dentry *devices;
|
||||
u32 wmid_devices;
|
||||
};
|
||||
|
||||
@@ -1002,6 +1001,7 @@ static acpi_status WMID_get_u32(u32 *value, u32 cap)
|
||||
*value = tmp & 0x1;
|
||||
return 0;
|
||||
}
|
||||
/* fall through */
|
||||
default:
|
||||
return AE_ERROR;
|
||||
}
|
||||
@@ -1328,6 +1328,7 @@ static acpi_status get_u32(u32 *value, u32 cap)
|
||||
status = AMW0_get_u32(value, cap);
|
||||
break;
|
||||
}
|
||||
/* fall through */
|
||||
case ACER_WMID:
|
||||
status = WMID_get_u32(value, cap);
|
||||
break;
|
||||
@@ -1370,6 +1371,7 @@ static acpi_status set_u32(u32 value, u32 cap)
|
||||
|
||||
return AMW0_set_u32(value, cap);
|
||||
}
|
||||
/* fall through */
|
||||
case ACER_WMID:
|
||||
return WMID_set_u32(value, cap);
|
||||
case ACER_WMID_v2:
|
||||
@@ -1379,6 +1381,7 @@ static acpi_status set_u32(u32 value, u32 cap)
|
||||
return wmid_v2_set_u32(value, cap);
|
||||
else if (wmi_has_guid(WMID_GUID2))
|
||||
return WMID_set_u32(value, cap);
|
||||
/* fall through */
|
||||
default:
|
||||
return AE_BAD_PARAMETER;
|
||||
}
|
||||
@@ -2148,29 +2151,15 @@ static struct platform_device *acer_platform_device;
|
||||
|
||||
static void remove_debugfs(void)
|
||||
{
|
||||
debugfs_remove(interface->debug.devices);
|
||||
debugfs_remove(interface->debug.root);
|
||||
debugfs_remove_recursive(interface->debug.root);
|
||||
}
|
||||
|
||||
static int __init create_debugfs(void)
|
||||
static void __init create_debugfs(void)
|
||||
{
|
||||
interface->debug.root = debugfs_create_dir("acer-wmi", NULL);
|
||||
if (!interface->debug.root) {
|
||||
pr_err("Failed to create debugfs directory");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
interface->debug.devices = debugfs_create_u32("devices", S_IRUGO,
|
||||
interface->debug.root,
|
||||
&interface->debug.wmid_devices);
|
||||
if (!interface->debug.devices)
|
||||
goto error_debugfs;
|
||||
|
||||
return 0;
|
||||
|
||||
error_debugfs:
|
||||
remove_debugfs();
|
||||
return -ENOMEM;
|
||||
debugfs_create_u32("devices", S_IRUGO, interface->debug.root,
|
||||
&interface->debug.wmid_devices);
|
||||
}
|
||||
|
||||
static int __init acer_wmi_init(void)
|
||||
@@ -2300,9 +2289,7 @@ static int __init acer_wmi_init(void)
|
||||
|
||||
if (wmi_has_guid(WMID_GUID2)) {
|
||||
interface->debug.wmid_devices = get_wmid_devices();
|
||||
err = create_debugfs();
|
||||
if (err)
|
||||
goto error_create_debugfs;
|
||||
create_debugfs();
|
||||
}
|
||||
|
||||
/* Override any initial settings with values from the commandline */
|
||||
@@ -2310,8 +2297,6 @@ static int __init acer_wmi_init(void)
|
||||
|
||||
return 0;
|
||||
|
||||
error_create_debugfs:
|
||||
platform_device_del(acer_platform_device);
|
||||
error_device_add:
|
||||
platform_device_put(acer_platform_device);
|
||||
error_device_alloc:
|
||||
|
@@ -463,6 +463,7 @@ static const struct key_entry asus_nb_wmi_keymap[] = {
|
||||
{ KE_KEY, 0x6B, { KEY_TOUCHPAD_TOGGLE } },
|
||||
{ KE_IGNORE, 0x6E, }, /* Low Battery notification */
|
||||
{ KE_KEY, 0x7a, { KEY_ALS_TOGGLE } }, /* Ambient Light Sensor Toggle */
|
||||
{ KE_KEY, 0x7c, { KEY_MICMUTE } },
|
||||
{ KE_KEY, 0x7D, { KEY_BLUETOOTH } }, /* Bluetooth Enable */
|
||||
{ KE_KEY, 0x7E, { KEY_BLUETOOTH } }, /* Bluetooth Disable */
|
||||
{ KE_KEY, 0x82, { KEY_CAMERA } },
|
||||
@@ -477,7 +478,7 @@ static const struct key_entry asus_nb_wmi_keymap[] = {
|
||||
{ KE_KEY, 0x92, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + DVI */
|
||||
{ KE_KEY, 0x93, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + DVI */
|
||||
{ KE_KEY, 0x95, { KEY_MEDIA } },
|
||||
{ KE_KEY, 0x99, { KEY_PHONE } },
|
||||
{ KE_KEY, 0x99, { KEY_PHONE } }, /* Conflicts with fan mode switch */
|
||||
{ KE_KEY, 0xA0, { KEY_SWITCHVIDEOMODE } }, /* SDSP HDMI only */
|
||||
{ KE_KEY, 0xA1, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + HDMI */
|
||||
{ KE_KEY, 0xA2, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + HDMI */
|
||||
|
@@ -57,6 +57,7 @@ MODULE_LICENSE("GPL");
|
||||
#define NOTIFY_KBD_BRTUP 0xc4
|
||||
#define NOTIFY_KBD_BRTDWN 0xc5
|
||||
#define NOTIFY_KBD_BRTTOGGLE 0xc7
|
||||
#define NOTIFY_KBD_FBM 0x99
|
||||
|
||||
#define ASUS_WMI_FNLOCK_BIOS_DISABLED BIT(0)
|
||||
|
||||
@@ -67,9 +68,27 @@ MODULE_LICENSE("GPL");
|
||||
#define ASUS_FAN_CTRL_MANUAL 1
|
||||
#define ASUS_FAN_CTRL_AUTO 2
|
||||
|
||||
#define ASUS_FAN_BOOST_MODE_NORMAL 0
|
||||
#define ASUS_FAN_BOOST_MODE_OVERBOOST 1
|
||||
#define ASUS_FAN_BOOST_MODE_OVERBOOST_MASK 0x01
|
||||
#define ASUS_FAN_BOOST_MODE_SILENT 2
|
||||
#define ASUS_FAN_BOOST_MODE_SILENT_MASK 0x02
|
||||
#define ASUS_FAN_BOOST_MODES_MASK 0x03
|
||||
|
||||
#define USB_INTEL_XUSB2PR 0xD0
|
||||
#define PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_XHCI 0x9c31
|
||||
|
||||
#define ASUS_ACPI_UID_ASUSWMI "ASUSWMI"
|
||||
#define ASUS_ACPI_UID_ATK "ATK"
|
||||
|
||||
#define WMI_EVENT_QUEUE_SIZE 0x10
|
||||
#define WMI_EVENT_QUEUE_END 0x1
|
||||
#define WMI_EVENT_MASK 0xFFFF
|
||||
/* The WMI hotkey event value is always the same. */
|
||||
#define WMI_EVENT_VALUE_ATK 0xFF
|
||||
|
||||
#define WMI_EVENT_MASK 0xFFFF
|
||||
|
||||
static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL };
|
||||
|
||||
static bool ashs_present(void)
|
||||
@@ -85,6 +104,7 @@ static bool ashs_present(void)
|
||||
struct bios_args {
|
||||
u32 arg0;
|
||||
u32 arg1;
|
||||
u32 arg2; /* At least TUF Gaming series uses 3 dword input buffer. */
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
@@ -132,6 +152,7 @@ struct asus_wmi {
|
||||
int dsts_id;
|
||||
int spec;
|
||||
int sfun;
|
||||
bool wmi_event_queue;
|
||||
|
||||
struct input_dev *inputdev;
|
||||
struct backlight_device *backlight_device;
|
||||
@@ -161,6 +182,10 @@ struct asus_wmi {
|
||||
int asus_hwmon_num_fans;
|
||||
int asus_hwmon_pwm;
|
||||
|
||||
bool fan_boost_mode_available;
|
||||
u8 fan_boost_mode_mask;
|
||||
u8 fan_boost_mode;
|
||||
|
||||
struct hotplug_slot hotplug_slot;
|
||||
struct mutex hotplug_lock;
|
||||
struct mutex wmi_lock;
|
||||
@@ -174,6 +199,8 @@ struct asus_wmi {
|
||||
struct asus_wmi_driver *driver;
|
||||
};
|
||||
|
||||
/* Input **********************************************************************/
|
||||
|
||||
static int asus_wmi_input_init(struct asus_wmi *asus)
|
||||
{
|
||||
int err;
|
||||
@@ -211,11 +238,15 @@ static void asus_wmi_input_exit(struct asus_wmi *asus)
|
||||
asus->inputdev = NULL;
|
||||
}
|
||||
|
||||
int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval)
|
||||
/* WMI ************************************************************************/
|
||||
|
||||
static int asus_wmi_evaluate_method3(u32 method_id,
|
||||
u32 arg0, u32 arg1, u32 arg2, u32 *retval)
|
||||
{
|
||||
struct bios_args args = {
|
||||
.arg0 = arg0,
|
||||
.arg1 = arg1,
|
||||
.arg2 = arg2,
|
||||
};
|
||||
struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
|
||||
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
@@ -227,7 +258,7 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval)
|
||||
&input, &output);
|
||||
|
||||
if (ACPI_FAILURE(status))
|
||||
goto exit;
|
||||
return -EIO;
|
||||
|
||||
obj = (union acpi_object *)output.pointer;
|
||||
if (obj && obj->type == ACPI_TYPE_INTEGER)
|
||||
@@ -238,15 +269,16 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval)
|
||||
|
||||
kfree(obj);
|
||||
|
||||
exit:
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
|
||||
if (tmp == ASUS_WMI_UNSUPPORTED_METHOD)
|
||||
return -ENODEV;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval)
|
||||
{
|
||||
return asus_wmi_evaluate_method3(method_id, arg0, arg1, 0, retval);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(asus_wmi_evaluate_method);
|
||||
|
||||
static int asus_wmi_evaluate_method_agfn(const struct acpi_buffer args)
|
||||
@@ -320,9 +352,8 @@ static int asus_wmi_get_devstate_simple(struct asus_wmi *asus, u32 dev_id)
|
||||
ASUS_WMI_DSTS_STATUS_BIT);
|
||||
}
|
||||
|
||||
/*
|
||||
* LEDs
|
||||
*/
|
||||
/* LEDs ***********************************************************************/
|
||||
|
||||
/*
|
||||
* These functions actually update the LED's, and are called from a
|
||||
* workqueue. By doing this as separate work rather than when the LED
|
||||
@@ -427,6 +458,10 @@ static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
|
||||
static void kbd_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
/* Prevent disabling keyboard backlight on module unregister */
|
||||
if (led_cdev->flags & LED_UNREGISTERING)
|
||||
return;
|
||||
|
||||
do_kbd_led_set(led_cdev, value);
|
||||
}
|
||||
|
||||
@@ -582,8 +617,7 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
|
||||
goto error;
|
||||
}
|
||||
|
||||
led_val = kbd_led_read(asus, NULL, NULL);
|
||||
if (led_val >= 0) {
|
||||
if (!kbd_led_read(asus, &led_val, NULL)) {
|
||||
asus->kbd_led_wk = led_val;
|
||||
asus->kbd_led.name = "asus::kbd_backlight";
|
||||
asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
|
||||
@@ -633,6 +667,7 @@ error:
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* RF *************************************************************************/
|
||||
|
||||
/*
|
||||
* PCI hotplug (for wlan rfkill)
|
||||
@@ -1055,6 +1090,8 @@ exit:
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Quirks *********************************************************************/
|
||||
|
||||
static void asus_wmi_set_xusb2pr(struct asus_wmi *asus)
|
||||
{
|
||||
struct pci_dev *xhci_pdev;
|
||||
@@ -1087,9 +1124,8 @@ static void asus_wmi_set_als(void)
|
||||
asus_wmi_set_devstate(ASUS_WMI_DEVID_ALS_ENABLE, 1, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Hwmon device
|
||||
*/
|
||||
/* Hwmon device ***************************************************************/
|
||||
|
||||
static int asus_hwmon_agfn_fan_speed_read(struct asus_wmi *asus, int fan,
|
||||
int *speed)
|
||||
{
|
||||
@@ -1353,8 +1389,7 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj,
|
||||
struct attribute *attr, int idx)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct platform_device *pdev = to_platform_device(dev->parent);
|
||||
struct asus_wmi *asus = platform_get_drvdata(pdev);
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev->parent);
|
||||
int dev_id = -1;
|
||||
int fan_attr = -1;
|
||||
u32 value = ASUS_WMI_UNSUPPORTED_METHOD;
|
||||
@@ -1395,8 +1430,11 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj,
|
||||
else
|
||||
ok = fan_attr <= asus->asus_hwmon_num_fans;
|
||||
} else if (dev_id == ASUS_WMI_DEVID_THERMAL_CTRL) {
|
||||
/* If value is zero, something is clearly wrong */
|
||||
if (!value)
|
||||
/*
|
||||
* If the temperature value in deci-Kelvin is near the absolute
|
||||
* zero temperature, something is clearly wrong
|
||||
*/
|
||||
if (value == 0 || value == 1)
|
||||
ok = false;
|
||||
} else if (fan_attr <= asus->asus_hwmon_num_fans && fan_attr != -1) {
|
||||
ok = true;
|
||||
@@ -1415,11 +1453,12 @@ __ATTRIBUTE_GROUPS(hwmon_attribute);
|
||||
|
||||
static int asus_wmi_hwmon_init(struct asus_wmi *asus)
|
||||
{
|
||||
struct device *dev = &asus->platform_device->dev;
|
||||
struct device *hwmon;
|
||||
|
||||
hwmon = hwmon_device_register_with_groups(&asus->platform_device->dev,
|
||||
"asus", asus,
|
||||
hwmon_attribute_groups);
|
||||
hwmon = devm_hwmon_device_register_with_groups(dev, "asus", asus,
|
||||
hwmon_attribute_groups);
|
||||
|
||||
if (IS_ERR(hwmon)) {
|
||||
pr_err("Could not register asus hwmon device\n");
|
||||
return PTR_ERR(hwmon);
|
||||
@@ -1427,9 +1466,143 @@ static int asus_wmi_hwmon_init(struct asus_wmi *asus)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Backlight
|
||||
*/
|
||||
static int asus_wmi_fan_init(struct asus_wmi *asus)
|
||||
{
|
||||
int status;
|
||||
|
||||
asus->asus_hwmon_pwm = -1;
|
||||
asus->asus_hwmon_num_fans = -1;
|
||||
asus->asus_hwmon_fan_manual_mode = false;
|
||||
|
||||
status = asus_hwmon_get_fan_number(asus, &asus->asus_hwmon_num_fans);
|
||||
if (status) {
|
||||
asus->asus_hwmon_num_fans = 0;
|
||||
pr_warn("Could not determine number of fans: %d\n", status);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
pr_info("Number of fans: %d\n", asus->asus_hwmon_num_fans);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Fan mode *******************************************************************/
|
||||
|
||||
static int fan_boost_mode_check_present(struct asus_wmi *asus)
|
||||
{
|
||||
u32 result;
|
||||
int err;
|
||||
|
||||
asus->fan_boost_mode_available = false;
|
||||
|
||||
err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_BOOST_MODE,
|
||||
&result);
|
||||
if (err) {
|
||||
if (err == -ENODEV)
|
||||
return 0;
|
||||
else
|
||||
return err;
|
||||
}
|
||||
|
||||
if ((result & ASUS_WMI_DSTS_PRESENCE_BIT) &&
|
||||
(result & ASUS_FAN_BOOST_MODES_MASK)) {
|
||||
asus->fan_boost_mode_available = true;
|
||||
asus->fan_boost_mode_mask = result & ASUS_FAN_BOOST_MODES_MASK;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fan_boost_mode_write(struct asus_wmi *asus)
|
||||
{
|
||||
int err;
|
||||
u8 value;
|
||||
u32 retval;
|
||||
|
||||
value = asus->fan_boost_mode;
|
||||
|
||||
pr_info("Set fan boost mode: %u\n", value);
|
||||
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_FAN_BOOST_MODE, value,
|
||||
&retval);
|
||||
|
||||
if (err) {
|
||||
pr_warn("Failed to set fan boost mode: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (retval != 1) {
|
||||
pr_warn("Failed to set fan boost mode (retval): 0x%x\n",
|
||||
retval);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fan_boost_mode_switch_next(struct asus_wmi *asus)
|
||||
{
|
||||
u8 mask = asus->fan_boost_mode_mask;
|
||||
|
||||
if (asus->fan_boost_mode == ASUS_FAN_BOOST_MODE_NORMAL) {
|
||||
if (mask & ASUS_FAN_BOOST_MODE_OVERBOOST_MASK)
|
||||
asus->fan_boost_mode = ASUS_FAN_BOOST_MODE_OVERBOOST;
|
||||
else if (mask & ASUS_FAN_BOOST_MODE_SILENT_MASK)
|
||||
asus->fan_boost_mode = ASUS_FAN_BOOST_MODE_SILENT;
|
||||
} else if (asus->fan_boost_mode == ASUS_FAN_BOOST_MODE_OVERBOOST) {
|
||||
if (mask & ASUS_FAN_BOOST_MODE_SILENT_MASK)
|
||||
asus->fan_boost_mode = ASUS_FAN_BOOST_MODE_SILENT;
|
||||
else
|
||||
asus->fan_boost_mode = ASUS_FAN_BOOST_MODE_NORMAL;
|
||||
} else {
|
||||
asus->fan_boost_mode = ASUS_FAN_BOOST_MODE_NORMAL;
|
||||
}
|
||||
|
||||
return fan_boost_mode_write(asus);
|
||||
}
|
||||
|
||||
static ssize_t fan_boost_mode_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n", asus->fan_boost_mode);
|
||||
}
|
||||
|
||||
static ssize_t fan_boost_mode_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int result;
|
||||
u8 new_mode;
|
||||
struct asus_wmi *asus = dev_get_drvdata(dev);
|
||||
u8 mask = asus->fan_boost_mode_mask;
|
||||
|
||||
result = kstrtou8(buf, 10, &new_mode);
|
||||
if (result < 0) {
|
||||
pr_warn("Trying to store invalid value\n");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (new_mode == ASUS_FAN_BOOST_MODE_OVERBOOST) {
|
||||
if (!(mask & ASUS_FAN_BOOST_MODE_OVERBOOST_MASK))
|
||||
return -EINVAL;
|
||||
} else if (new_mode == ASUS_FAN_BOOST_MODE_SILENT) {
|
||||
if (!(mask & ASUS_FAN_BOOST_MODE_SILENT_MASK))
|
||||
return -EINVAL;
|
||||
} else if (new_mode != ASUS_FAN_BOOST_MODE_NORMAL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
asus->fan_boost_mode = new_mode;
|
||||
fan_boost_mode_write(asus);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Fan boost mode: 0 - normal, 1 - overboost, 2 - silent
|
||||
static DEVICE_ATTR_RW(fan_boost_mode);
|
||||
|
||||
/* Backlight ******************************************************************/
|
||||
|
||||
static int read_backlight_power(struct asus_wmi *asus)
|
||||
{
|
||||
int ret;
|
||||
@@ -1611,6 +1784,8 @@ static int is_display_toggle(int code)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Fn-lock ********************************************************************/
|
||||
|
||||
static bool asus_wmi_has_fnlock_key(struct asus_wmi *asus)
|
||||
{
|
||||
u32 result;
|
||||
@@ -1628,88 +1803,148 @@ static void asus_wmi_fnlock_update(struct asus_wmi *asus)
|
||||
asus_wmi_set_devstate(ASUS_WMI_DEVID_FNLOCK, mode, NULL);
|
||||
}
|
||||
|
||||
static void asus_wmi_notify(u32 value, void *context)
|
||||
/* WMI events *****************************************************************/
|
||||
|
||||
static int asus_wmi_get_event_code(u32 value)
|
||||
{
|
||||
struct asus_wmi *asus = context;
|
||||
struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
union acpi_object *obj;
|
||||
acpi_status status;
|
||||
int code;
|
||||
int orig_code;
|
||||
unsigned int key_value = 1;
|
||||
bool autorelease = 1;
|
||||
|
||||
status = wmi_get_event_data(value, &response);
|
||||
if (status != AE_OK) {
|
||||
pr_err("bad event status 0x%x\n", status);
|
||||
return;
|
||||
if (ACPI_FAILURE(status)) {
|
||||
pr_warn("Failed to get WMI notify code: %s\n",
|
||||
acpi_format_exception(status));
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
obj = (union acpi_object *)response.pointer;
|
||||
|
||||
if (!obj || obj->type != ACPI_TYPE_INTEGER)
|
||||
goto exit;
|
||||
if (obj && obj->type == ACPI_TYPE_INTEGER)
|
||||
code = (int)(obj->integer.value & WMI_EVENT_MASK);
|
||||
else
|
||||
code = -EIO;
|
||||
|
||||
kfree(obj);
|
||||
return code;
|
||||
}
|
||||
|
||||
static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
|
||||
{
|
||||
int orig_code;
|
||||
unsigned int key_value = 1;
|
||||
bool autorelease = 1;
|
||||
|
||||
code = obj->integer.value;
|
||||
orig_code = code;
|
||||
|
||||
if (asus->driver->key_filter) {
|
||||
asus->driver->key_filter(asus->driver, &code, &key_value,
|
||||
&autorelease);
|
||||
if (code == ASUS_WMI_KEY_IGNORE)
|
||||
goto exit;
|
||||
return;
|
||||
}
|
||||
|
||||
if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX)
|
||||
code = ASUS_WMI_BRN_UP;
|
||||
else if (code >= NOTIFY_BRNDOWN_MIN &&
|
||||
code <= NOTIFY_BRNDOWN_MAX)
|
||||
else if (code >= NOTIFY_BRNDOWN_MIN && code <= NOTIFY_BRNDOWN_MAX)
|
||||
code = ASUS_WMI_BRN_DOWN;
|
||||
|
||||
if (code == ASUS_WMI_BRN_DOWN || code == ASUS_WMI_BRN_UP) {
|
||||
if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
|
||||
asus_wmi_backlight_notify(asus, orig_code);
|
||||
goto exit;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (code == NOTIFY_KBD_BRTUP) {
|
||||
kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
|
||||
goto exit;
|
||||
return;
|
||||
}
|
||||
if (code == NOTIFY_KBD_BRTDWN) {
|
||||
kbd_led_set_by_kbd(asus, asus->kbd_led_wk - 1);
|
||||
goto exit;
|
||||
return;
|
||||
}
|
||||
if (code == NOTIFY_KBD_BRTTOGGLE) {
|
||||
if (asus->kbd_led_wk == asus->kbd_led.max_brightness)
|
||||
kbd_led_set_by_kbd(asus, 0);
|
||||
else
|
||||
kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
|
||||
goto exit;
|
||||
return;
|
||||
}
|
||||
|
||||
if (code == NOTIFY_FNLOCK_TOGGLE) {
|
||||
asus->fnlock_locked = !asus->fnlock_locked;
|
||||
asus_wmi_fnlock_update(asus);
|
||||
goto exit;
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_display_toggle(code) &&
|
||||
asus->driver->quirks->no_display_toggle)
|
||||
goto exit;
|
||||
if (asus->fan_boost_mode_available && code == NOTIFY_KBD_FBM) {
|
||||
fan_boost_mode_switch_next(asus);
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_display_toggle(code) && asus->driver->quirks->no_display_toggle)
|
||||
return;
|
||||
|
||||
if (!sparse_keymap_report_event(asus->inputdev, code,
|
||||
key_value, autorelease))
|
||||
pr_info("Unknown key %x pressed\n", code);
|
||||
|
||||
exit:
|
||||
kfree(obj);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sys helpers
|
||||
*/
|
||||
static void asus_wmi_notify(u32 value, void *context)
|
||||
{
|
||||
struct asus_wmi *asus = context;
|
||||
int code;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < WMI_EVENT_QUEUE_SIZE + 1; i++) {
|
||||
code = asus_wmi_get_event_code(value);
|
||||
|
||||
if (code < 0) {
|
||||
pr_warn("Failed to get notify code: %d\n", code);
|
||||
return;
|
||||
}
|
||||
|
||||
if (code == WMI_EVENT_QUEUE_END || code == WMI_EVENT_MASK)
|
||||
return;
|
||||
|
||||
asus_wmi_handle_event_code(code, asus);
|
||||
|
||||
/*
|
||||
* Double check that queue is present:
|
||||
* ATK (with queue) uses 0xff, ASUSWMI (without) 0xd2.
|
||||
*/
|
||||
if (!asus->wmi_event_queue || value != WMI_EVENT_VALUE_ATK)
|
||||
return;
|
||||
}
|
||||
|
||||
pr_warn("Failed to process event queue, last code: 0x%x\n", code);
|
||||
}
|
||||
|
||||
static int asus_wmi_notify_queue_flush(struct asus_wmi *asus)
|
||||
{
|
||||
int code;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < WMI_EVENT_QUEUE_SIZE + 1; i++) {
|
||||
code = asus_wmi_get_event_code(WMI_EVENT_VALUE_ATK);
|
||||
|
||||
if (code < 0) {
|
||||
pr_warn("Failed to get event during flush: %d\n", code);
|
||||
return code;
|
||||
}
|
||||
|
||||
if (code == WMI_EVENT_QUEUE_END || code == WMI_EVENT_MASK)
|
||||
return 0;
|
||||
}
|
||||
|
||||
pr_warn("Failed to flush event queue\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Sysfs **********************************************************************/
|
||||
|
||||
static int parse_arg(const char *buf, unsigned long count, int *val)
|
||||
{
|
||||
if (!count)
|
||||
@@ -1805,6 +2040,7 @@ static struct attribute *platform_attributes[] = {
|
||||
&dev_attr_touchpad.attr,
|
||||
&dev_attr_lid_resume.attr,
|
||||
&dev_attr_als_enable.attr,
|
||||
&dev_attr_fan_boost_mode.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -1826,6 +2062,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
|
||||
devid = ASUS_WMI_DEVID_LID_RESUME;
|
||||
else if (attr == &dev_attr_als_enable.attr)
|
||||
devid = ASUS_WMI_DEVID_ALS_ENABLE;
|
||||
else if (attr == &dev_attr_fan_boost_mode.attr)
|
||||
ok = asus->fan_boost_mode_available;
|
||||
|
||||
if (devid != -1)
|
||||
ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0);
|
||||
@@ -1848,11 +2086,12 @@ static int asus_wmi_sysfs_init(struct platform_device *device)
|
||||
return sysfs_create_group(&device->dev.kobj, &platform_attribute_group);
|
||||
}
|
||||
|
||||
/*
|
||||
* Platform device
|
||||
*/
|
||||
/* Platform device ************************************************************/
|
||||
|
||||
static int asus_wmi_platform_init(struct asus_wmi *asus)
|
||||
{
|
||||
struct device *dev = &asus->platform_device->dev;
|
||||
char *wmi_uid;
|
||||
int rv;
|
||||
|
||||
/* INIT enable hotkeys on some models */
|
||||
@@ -1882,11 +2121,41 @@ static int asus_wmi_platform_init(struct asus_wmi *asus)
|
||||
* Note, on most Eeepc, there is no way to check if a method exist
|
||||
* or note, while on notebooks, they returns 0xFFFFFFFE on failure,
|
||||
* but once again, SPEC may probably be used for that kind of things.
|
||||
*
|
||||
* Additionally at least TUF Gaming series laptops return nothing for
|
||||
* unknown methods, so the detection in this way is not possible.
|
||||
*
|
||||
* There is strong indication that only ACPI WMI devices that have _UID
|
||||
* equal to "ASUSWMI" use DCTS whereas those with "ATK" use DSTS.
|
||||
*/
|
||||
if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, 0, 0, NULL))
|
||||
wmi_uid = wmi_get_acpi_device_uid(ASUS_WMI_MGMT_GUID);
|
||||
if (!wmi_uid)
|
||||
return -ENODEV;
|
||||
|
||||
if (!strcmp(wmi_uid, ASUS_ACPI_UID_ASUSWMI)) {
|
||||
dev_info(dev, "Detected ASUSWMI, use DCTS\n");
|
||||
asus->dsts_id = ASUS_WMI_METHODID_DCTS;
|
||||
} else {
|
||||
dev_info(dev, "Detected %s, not ASUSWMI, use DSTS\n", wmi_uid);
|
||||
asus->dsts_id = ASUS_WMI_METHODID_DSTS;
|
||||
else
|
||||
asus->dsts_id = ASUS_WMI_METHODID_DSTS2;
|
||||
}
|
||||
|
||||
/*
|
||||
* Some devices can have multiple event codes stored in a queue before
|
||||
* the module load if it was unloaded intermittently after calling
|
||||
* the INIT method (enables event handling). The WMI notify handler is
|
||||
* expected to retrieve all event codes until a retrieved code equals
|
||||
* queue end marker (One or Ones). Old codes are flushed from the queue
|
||||
* upon module load. Not enabling this when it should be has minimal
|
||||
* visible impact so fall back if anything goes wrong.
|
||||
*/
|
||||
wmi_uid = wmi_get_acpi_device_uid(asus->driver->event_guid);
|
||||
if (wmi_uid && !strcmp(wmi_uid, ASUS_ACPI_UID_ATK)) {
|
||||
dev_info(dev, "Detected ATK, enable event queue\n");
|
||||
|
||||
if (!asus_wmi_notify_queue_flush(asus))
|
||||
asus->wmi_event_queue = true;
|
||||
}
|
||||
|
||||
/* CWAP allow to define the behavior of the Fn+F2 key,
|
||||
* this method doesn't seems to be present on Eee PCs */
|
||||
@@ -1894,17 +2163,11 @@ static int asus_wmi_platform_init(struct asus_wmi *asus)
|
||||
asus_wmi_set_devstate(ASUS_WMI_DEVID_CWAP,
|
||||
asus->driver->quirks->wapf, NULL);
|
||||
|
||||
return asus_wmi_sysfs_init(asus->platform_device);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void asus_wmi_platform_exit(struct asus_wmi *asus)
|
||||
{
|
||||
asus_wmi_sysfs_exit(asus->platform_device);
|
||||
}
|
||||
/* debugfs ********************************************************************/
|
||||
|
||||
/*
|
||||
* debugfs
|
||||
*/
|
||||
struct asus_wmi_debugfs_node {
|
||||
struct asus_wmi *asus;
|
||||
char *name;
|
||||
@@ -2005,74 +2268,33 @@ static void asus_wmi_debugfs_exit(struct asus_wmi *asus)
|
||||
debugfs_remove_recursive(asus->debug.root);
|
||||
}
|
||||
|
||||
static int asus_wmi_debugfs_init(struct asus_wmi *asus)
|
||||
static void asus_wmi_debugfs_init(struct asus_wmi *asus)
|
||||
{
|
||||
struct dentry *dent;
|
||||
int i;
|
||||
|
||||
asus->debug.root = debugfs_create_dir(asus->driver->name, NULL);
|
||||
if (!asus->debug.root) {
|
||||
pr_err("failed to create debugfs directory\n");
|
||||
goto error_debugfs;
|
||||
}
|
||||
|
||||
dent = debugfs_create_x32("method_id", S_IRUGO | S_IWUSR,
|
||||
asus->debug.root, &asus->debug.method_id);
|
||||
if (!dent)
|
||||
goto error_debugfs;
|
||||
debugfs_create_x32("method_id", S_IRUGO | S_IWUSR, asus->debug.root,
|
||||
&asus->debug.method_id);
|
||||
|
||||
dent = debugfs_create_x32("dev_id", S_IRUGO | S_IWUSR,
|
||||
asus->debug.root, &asus->debug.dev_id);
|
||||
if (!dent)
|
||||
goto error_debugfs;
|
||||
debugfs_create_x32("dev_id", S_IRUGO | S_IWUSR, asus->debug.root,
|
||||
&asus->debug.dev_id);
|
||||
|
||||
dent = debugfs_create_x32("ctrl_param", S_IRUGO | S_IWUSR,
|
||||
asus->debug.root, &asus->debug.ctrl_param);
|
||||
if (!dent)
|
||||
goto error_debugfs;
|
||||
debugfs_create_x32("ctrl_param", S_IRUGO | S_IWUSR, asus->debug.root,
|
||||
&asus->debug.ctrl_param);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(asus_wmi_debug_files); i++) {
|
||||
struct asus_wmi_debugfs_node *node = &asus_wmi_debug_files[i];
|
||||
|
||||
node->asus = asus;
|
||||
dent = debugfs_create_file(node->name, S_IFREG | S_IRUGO,
|
||||
asus->debug.root, node,
|
||||
&asus_wmi_debugfs_io_ops);
|
||||
if (!dent) {
|
||||
pr_err("failed to create debug file: %s\n", node->name);
|
||||
goto error_debugfs;
|
||||
}
|
||||
debugfs_create_file(node->name, S_IFREG | S_IRUGO,
|
||||
asus->debug.root, node,
|
||||
&asus_wmi_debugfs_io_ops);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error_debugfs:
|
||||
asus_wmi_debugfs_exit(asus);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static int asus_wmi_fan_init(struct asus_wmi *asus)
|
||||
{
|
||||
int status;
|
||||
/* Init / exit ****************************************************************/
|
||||
|
||||
asus->asus_hwmon_pwm = -1;
|
||||
asus->asus_hwmon_num_fans = -1;
|
||||
asus->asus_hwmon_fan_manual_mode = false;
|
||||
|
||||
status = asus_hwmon_get_fan_number(asus, &asus->asus_hwmon_num_fans);
|
||||
if (status) {
|
||||
asus->asus_hwmon_num_fans = 0;
|
||||
pr_warn("Could not determine number of fans: %d\n", status);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
pr_info("Number of fans: %d\n", asus->asus_hwmon_num_fans);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* WMI Driver
|
||||
*/
|
||||
static int asus_wmi_add(struct platform_device *pdev)
|
||||
{
|
||||
struct platform_driver *pdrv = to_platform_driver(pdev->dev.driver);
|
||||
@@ -2099,6 +2321,14 @@ static int asus_wmi_add(struct platform_device *pdev)
|
||||
if (err)
|
||||
goto fail_platform;
|
||||
|
||||
err = fan_boost_mode_check_present(asus);
|
||||
if (err)
|
||||
goto fail_fan_boost_mode;
|
||||
|
||||
err = asus_wmi_sysfs_init(asus->platform_device);
|
||||
if (err)
|
||||
goto fail_sysfs;
|
||||
|
||||
err = asus_wmi_input_init(asus);
|
||||
if (err)
|
||||
goto fail_input;
|
||||
@@ -2162,14 +2392,10 @@ static int asus_wmi_add(struct platform_device *pdev)
|
||||
goto fail_wmi_handler;
|
||||
}
|
||||
|
||||
err = asus_wmi_debugfs_init(asus);
|
||||
if (err)
|
||||
goto fail_debugfs;
|
||||
asus_wmi_debugfs_init(asus);
|
||||
|
||||
return 0;
|
||||
|
||||
fail_debugfs:
|
||||
wmi_remove_notify_handler(asus->driver->event_guid);
|
||||
fail_wmi_handler:
|
||||
asus_wmi_backlight_exit(asus);
|
||||
fail_backlight:
|
||||
@@ -2180,7 +2406,9 @@ fail_leds:
|
||||
fail_hwmon:
|
||||
asus_wmi_input_exit(asus);
|
||||
fail_input:
|
||||
asus_wmi_platform_exit(asus);
|
||||
asus_wmi_sysfs_exit(asus->platform_device);
|
||||
fail_sysfs:
|
||||
fail_fan_boost_mode:
|
||||
fail_platform:
|
||||
kfree(asus);
|
||||
return err;
|
||||
@@ -2197,16 +2425,15 @@ static int asus_wmi_remove(struct platform_device *device)
|
||||
asus_wmi_led_exit(asus);
|
||||
asus_wmi_rfkill_exit(asus);
|
||||
asus_wmi_debugfs_exit(asus);
|
||||
asus_wmi_platform_exit(asus);
|
||||
asus_wmi_sysfs_exit(asus->platform_device);
|
||||
asus_hwmon_fan_set_auto(asus);
|
||||
|
||||
kfree(asus);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Platform driver - hibernate/resume callbacks
|
||||
*/
|
||||
/* Platform driver - hibernate/resume callbacks *******************************/
|
||||
|
||||
static int asus_hotk_thaw(struct device *device)
|
||||
{
|
||||
struct asus_wmi *asus = dev_get_drvdata(device);
|
||||
@@ -2282,6 +2509,8 @@ static const struct dev_pm_ops asus_pm_ops = {
|
||||
.resume = asus_hotk_resume,
|
||||
};
|
||||
|
||||
/* Registration ***************************************************************/
|
||||
|
||||
static int asus_wmi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct platform_driver *pdrv = to_platform_driver(pdev->dev.driver);
|
||||
|
@@ -7,7 +7,7 @@
|
||||
* and Host Control Actions (power cycle or power off after OS shutdown) on
|
||||
* Dell systems.
|
||||
*
|
||||
* See Documentation/dcdbas.txt for more information.
|
||||
* See Documentation/driver-api/dcdbas.rst for more information.
|
||||
*
|
||||
* Copyright (C) 1995-2006 Dell Inc.
|
||||
*/
|
||||
|
@@ -2173,9 +2173,8 @@ static int __init dell_init(void)
|
||||
kbd_led_init(&platform_device->dev);
|
||||
|
||||
dell_laptop_dir = debugfs_create_dir("dell_laptop", NULL);
|
||||
if (dell_laptop_dir != NULL)
|
||||
debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL,
|
||||
&dell_debugfs_fops);
|
||||
debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL,
|
||||
&dell_debugfs_fops);
|
||||
|
||||
dell_laptop_register_notifier(&dell_laptop_notifier);
|
||||
|
||||
|
@@ -143,7 +143,7 @@ fail_smbios_cmd:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dell_smbios_wmi_probe(struct wmi_device *wdev)
|
||||
static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context)
|
||||
{
|
||||
struct wmi_driver *wdriver =
|
||||
container_of(wdev->dev.driver, struct wmi_driver, driver);
|
||||
|
@@ -198,6 +198,7 @@ static int smo8800_remove(struct acpi_device *device)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* NOTE: Keep this list in sync with drivers/i2c/busses/i2c-i801.c */
|
||||
static const struct acpi_device_id smo8800_ids[] = {
|
||||
{ "SMO8800", 0 },
|
||||
{ "SMO8801", 0 },
|
||||
|
@@ -98,7 +98,8 @@ EXPORT_SYMBOL_GPL(dell_wmi_get_hotfix);
|
||||
* WMI buffer length 12 4 <length>
|
||||
* WMI hotfix number 16 4 <hotfix>
|
||||
*/
|
||||
static int dell_wmi_descriptor_probe(struct wmi_device *wdev)
|
||||
static int dell_wmi_descriptor_probe(struct wmi_device *wdev,
|
||||
const void *context)
|
||||
{
|
||||
union acpi_object *obj = NULL;
|
||||
struct descriptor_priv *priv;
|
||||
|
@@ -659,7 +659,7 @@ static int dell_wmi_events_set_enabled(bool enable)
|
||||
return dell_smbios_error(ret);
|
||||
}
|
||||
|
||||
static int dell_wmi_probe(struct wmi_device *wdev)
|
||||
static int dell_wmi_probe(struct wmi_device *wdev, const void *context)
|
||||
{
|
||||
struct dell_wmi_priv *priv;
|
||||
int ret;
|
||||
|
@@ -24,7 +24,7 @@
|
||||
* on every time the packet data is written. This driver requires an
|
||||
* application to break the BIOS image in to fixed sized packet chunks.
|
||||
*
|
||||
* See Documentation/dell_rbu.txt for more info.
|
||||
* See Documentation/driver-api/dell_rbu.rst for more info.
|
||||
*/
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
|
@@ -229,6 +229,7 @@ static const struct dmi_system_id lis3lv02d_dmi_ids[] = {
|
||||
AXIS_DMI_MATCH("HPB440G3", "HP ProBook 440 G3", x_inverted_usd),
|
||||
AXIS_DMI_MATCH("HPB440G4", "HP ProBook 440 G4", x_inverted),
|
||||
AXIS_DMI_MATCH("HPB442x", "HP ProBook 442", xy_rotated_left),
|
||||
AXIS_DMI_MATCH("HPB450G0", "HP ProBook 450 G0", x_inverted),
|
||||
AXIS_DMI_MATCH("HPB452x", "HP ProBook 452", y_inverted),
|
||||
AXIS_DMI_MATCH("HPB522x", "HP ProBook 522", xy_swap),
|
||||
AXIS_DMI_MATCH("HPB532x", "HP ProBook 532", y_inverted),
|
||||
|
@@ -166,7 +166,7 @@ static int huawei_wmi_input_setup(struct wmi_device *wdev)
|
||||
return input_register_device(priv->idev);
|
||||
}
|
||||
|
||||
static int huawei_wmi_probe(struct wmi_device *wdev)
|
||||
static int huawei_wmi_probe(struct wmi_device *wdev, const void *context)
|
||||
{
|
||||
struct huawei_wmi_priv *priv;
|
||||
int err;
|
||||
|
@@ -316,34 +316,15 @@ static int debugfs_cfg_show(struct seq_file *s, void *data)
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(debugfs_cfg);
|
||||
|
||||
static int ideapad_debugfs_init(struct ideapad_private *priv)
|
||||
static void ideapad_debugfs_init(struct ideapad_private *priv)
|
||||
{
|
||||
struct dentry *node;
|
||||
struct dentry *dir;
|
||||
|
||||
priv->debug = debugfs_create_dir("ideapad", NULL);
|
||||
if (priv->debug == NULL) {
|
||||
pr_err("failed to create debugfs directory");
|
||||
goto errout;
|
||||
}
|
||||
dir = debugfs_create_dir("ideapad", NULL);
|
||||
priv->debug = dir;
|
||||
|
||||
node = debugfs_create_file("cfg", S_IRUGO, priv->debug, priv,
|
||||
&debugfs_cfg_fops);
|
||||
if (!node) {
|
||||
pr_err("failed to create cfg in debugfs");
|
||||
goto errout;
|
||||
}
|
||||
|
||||
node = debugfs_create_file("status", S_IRUGO, priv->debug, priv,
|
||||
&debugfs_status_fops);
|
||||
if (!node) {
|
||||
pr_err("failed to create status in debugfs");
|
||||
goto errout;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
errout:
|
||||
return -ENOMEM;
|
||||
debugfs_create_file("cfg", S_IRUGO, dir, priv, &debugfs_cfg_fops);
|
||||
debugfs_create_file("status", S_IRUGO, dir, priv, &debugfs_status_fops);
|
||||
}
|
||||
|
||||
static void ideapad_debugfs_exit(struct ideapad_private *priv)
|
||||
@@ -1012,9 +993,7 @@ static int ideapad_acpi_add(struct platform_device *pdev)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ideapad_debugfs_init(priv);
|
||||
if (ret)
|
||||
goto debugfs_failed;
|
||||
ideapad_debugfs_init(priv);
|
||||
|
||||
ret = ideapad_input_init(priv);
|
||||
if (ret)
|
||||
@@ -1071,7 +1050,6 @@ backlight_failed:
|
||||
ideapad_input_exit(priv);
|
||||
input_failed:
|
||||
ideapad_debugfs_exit(priv);
|
||||
debugfs_failed:
|
||||
ideapad_sysfs_exit(priv);
|
||||
return ret;
|
||||
}
|
||||
|
@@ -56,7 +56,8 @@ static const struct attribute_group tbt_attribute_group = {
|
||||
.attrs = tbt_attrs,
|
||||
};
|
||||
|
||||
static int intel_wmi_thunderbolt_probe(struct wmi_device *wdev)
|
||||
static int intel_wmi_thunderbolt_probe(struct wmi_device *wdev,
|
||||
const void *context)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@@ -21,18 +21,55 @@
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/usb/pd.h>
|
||||
|
||||
#define EXPECTED_PTYPE 4
|
||||
|
||||
enum {
|
||||
INT33FE_NODE_FUSB302,
|
||||
INT33FE_NODE_MAX17047,
|
||||
INT33FE_NODE_PI3USB30532,
|
||||
INT33FE_NODE_DISPLAYPORT,
|
||||
INT33FE_NODE_ROLE_SWITCH,
|
||||
INT33FE_NODE_USB_CONNECTOR,
|
||||
INT33FE_NODE_MAX,
|
||||
};
|
||||
|
||||
struct cht_int33fe_data {
|
||||
struct i2c_client *max17047;
|
||||
struct i2c_client *fusb302;
|
||||
struct i2c_client *pi3usb30532;
|
||||
/* Contain a list-head must be per device */
|
||||
struct device_connection connections[4];
|
||||
|
||||
struct fwnode_handle *dp;
|
||||
struct fwnode_handle *mux;
|
||||
};
|
||||
|
||||
static const struct software_node nodes[];
|
||||
|
||||
static const struct software_node_ref_args pi3usb30532_ref = {
|
||||
&nodes[INT33FE_NODE_PI3USB30532]
|
||||
};
|
||||
|
||||
static const struct software_node_ref_args dp_ref = {
|
||||
&nodes[INT33FE_NODE_DISPLAYPORT]
|
||||
};
|
||||
|
||||
static struct software_node_ref_args mux_ref;
|
||||
|
||||
static const struct software_node_reference usb_connector_refs[] = {
|
||||
{ "orientation-switch", 1, &pi3usb30532_ref},
|
||||
{ "mode-switch", 1, &pi3usb30532_ref},
|
||||
{ "displayport", 1, &dp_ref},
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node_reference fusb302_refs[] = {
|
||||
{ "usb-role-switch", 1, &mux_ref},
|
||||
{ }
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -63,14 +100,6 @@ static int cht_int33fe_check_for_max17047(struct device *dev, void *data)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct i2c_client *cht_int33fe_find_max17047(void)
|
||||
{
|
||||
struct i2c_client *max17047 = NULL;
|
||||
|
||||
i2c_for_each_dev(&max17047, cht_int33fe_check_for_max17047);
|
||||
return max17047;
|
||||
}
|
||||
|
||||
static const char * const max17047_suppliers[] = { "bq24190-charger" };
|
||||
|
||||
static const struct property_entry max17047_props[] = {
|
||||
@@ -80,18 +109,196 @@ static const struct property_entry max17047_props[] = {
|
||||
|
||||
static const struct property_entry fusb302_props[] = {
|
||||
PROPERTY_ENTRY_STRING("linux,extcon-name", "cht_wcove_pwrsrc"),
|
||||
PROPERTY_ENTRY_U32("fcs,max-sink-microvolt", 12000000),
|
||||
PROPERTY_ENTRY_U32("fcs,max-sink-microamp", 3000000),
|
||||
PROPERTY_ENTRY_U32("fcs,max-sink-microwatt", 36000000),
|
||||
{ }
|
||||
};
|
||||
|
||||
#define PDO_FIXED_FLAGS \
|
||||
(PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_USB_COMM)
|
||||
|
||||
static const u32 src_pdo[] = {
|
||||
PDO_FIXED(5000, 1500, PDO_FIXED_FLAGS),
|
||||
};
|
||||
|
||||
static const u32 snk_pdo[] = {
|
||||
PDO_FIXED(5000, 400, PDO_FIXED_FLAGS),
|
||||
PDO_VAR(5000, 12000, 3000),
|
||||
};
|
||||
|
||||
static const struct property_entry usb_connector_props[] = {
|
||||
PROPERTY_ENTRY_STRING("data-role", "dual"),
|
||||
PROPERTY_ENTRY_STRING("power-role", "dual"),
|
||||
PROPERTY_ENTRY_STRING("try-power-role", "sink"),
|
||||
PROPERTY_ENTRY_U32_ARRAY("source-pdos", src_pdo),
|
||||
PROPERTY_ENTRY_U32_ARRAY("sink-pdos", snk_pdo),
|
||||
PROPERTY_ENTRY_U32("op-sink-microwatt", 2500000),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct software_node nodes[] = {
|
||||
{ "fusb302", NULL, fusb302_props, fusb302_refs },
|
||||
{ "max17047", NULL, max17047_props },
|
||||
{ "pi3usb30532" },
|
||||
{ "displayport" },
|
||||
{ "usb-role-switch" },
|
||||
{ "connector", &nodes[0], usb_connector_props, usb_connector_refs },
|
||||
{ }
|
||||
};
|
||||
|
||||
static int cht_int33fe_setup_mux(struct cht_int33fe_data *data)
|
||||
{
|
||||
struct fwnode_handle *fwnode;
|
||||
struct device *dev;
|
||||
struct device *p;
|
||||
|
||||
fwnode = software_node_fwnode(&nodes[INT33FE_NODE_ROLE_SWITCH]);
|
||||
if (!fwnode)
|
||||
return -ENODEV;
|
||||
|
||||
/* First finding the platform device */
|
||||
p = bus_find_device_by_name(&platform_bus_type, NULL,
|
||||
"intel_xhci_usb_sw");
|
||||
if (!p)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
/* Then the mux child device */
|
||||
dev = device_find_child_by_name(p, "intel_xhci_usb_sw-role-switch");
|
||||
put_device(p);
|
||||
if (!dev)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
/* If there already is a node for the mux, using that one. */
|
||||
if (dev->fwnode)
|
||||
fwnode_remove_software_node(fwnode);
|
||||
else
|
||||
dev->fwnode = fwnode;
|
||||
|
||||
data->mux = fwnode_handle_get(dev->fwnode);
|
||||
put_device(dev);
|
||||
mux_ref.node = to_software_node(data->mux);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cht_int33fe_setup_dp(struct cht_int33fe_data *data)
|
||||
{
|
||||
struct fwnode_handle *fwnode;
|
||||
struct pci_dev *pdev;
|
||||
|
||||
fwnode = software_node_fwnode(&nodes[INT33FE_NODE_DISPLAYPORT]);
|
||||
if (!fwnode)
|
||||
return -ENODEV;
|
||||
|
||||
/* First let's find the GPU PCI device */
|
||||
pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, NULL);
|
||||
if (!pdev || pdev->vendor != PCI_VENDOR_ID_INTEL) {
|
||||
pci_dev_put(pdev);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Then the DP child device node */
|
||||
data->dp = device_get_named_child_node(&pdev->dev, "DD02");
|
||||
pci_dev_put(pdev);
|
||||
if (!data->dp)
|
||||
return -ENODEV;
|
||||
|
||||
fwnode->secondary = ERR_PTR(-ENODEV);
|
||||
data->dp->secondary = fwnode;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cht_int33fe_remove_nodes(struct cht_int33fe_data *data)
|
||||
{
|
||||
software_node_unregister_nodes(nodes);
|
||||
|
||||
if (data->mux) {
|
||||
fwnode_handle_put(data->mux);
|
||||
mux_ref.node = NULL;
|
||||
data->mux = NULL;
|
||||
}
|
||||
|
||||
if (data->dp) {
|
||||
data->dp->secondary = NULL;
|
||||
fwnode_handle_put(data->dp);
|
||||
data->dp = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int cht_int33fe_add_nodes(struct cht_int33fe_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = software_node_register_nodes(nodes);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* The devices that are not created in this driver need extra steps. */
|
||||
|
||||
/*
|
||||
* There is no ACPI device node for the USB role mux, so we need to find
|
||||
* the mux device and assign our node directly to it. That means we
|
||||
* depend on the mux driver. This function will return -PROBE_DEFER
|
||||
* until the mux device is registered.
|
||||
*/
|
||||
ret = cht_int33fe_setup_mux(data);
|
||||
if (ret)
|
||||
goto err_remove_nodes;
|
||||
|
||||
/*
|
||||
* The DP connector does have ACPI device node. In this case we can just
|
||||
* find that ACPI node and assign our node as the secondary node to it.
|
||||
*/
|
||||
ret = cht_int33fe_setup_dp(data);
|
||||
if (ret)
|
||||
goto err_remove_nodes;
|
||||
|
||||
return 0;
|
||||
|
||||
err_remove_nodes:
|
||||
cht_int33fe_remove_nodes(data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
cht_int33fe_register_max17047(struct device *dev, struct cht_int33fe_data *data)
|
||||
{
|
||||
struct i2c_client *max17047 = NULL;
|
||||
struct i2c_board_info board_info;
|
||||
struct fwnode_handle *fwnode;
|
||||
int ret;
|
||||
|
||||
fwnode = software_node_fwnode(&nodes[INT33FE_NODE_MAX17047]);
|
||||
if (!fwnode)
|
||||
return -ENODEV;
|
||||
|
||||
i2c_for_each_dev(&max17047, cht_int33fe_check_for_max17047);
|
||||
if (max17047) {
|
||||
/* Pre-existing i2c-client for the max17047, add device-props */
|
||||
fwnode->secondary = ERR_PTR(-ENODEV);
|
||||
max17047->dev.fwnode->secondary = fwnode;
|
||||
/* And re-probe to get the new device-props applied. */
|
||||
ret = device_reprobe(&max17047->dev);
|
||||
if (ret)
|
||||
dev_warn(dev, "Reprobing max17047 error: %d\n", ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
memset(&board_info, 0, sizeof(board_info));
|
||||
strlcpy(board_info.type, "max17047", I2C_NAME_SIZE);
|
||||
board_info.dev_name = "max17047";
|
||||
board_info.fwnode = fwnode;
|
||||
data->max17047 = i2c_acpi_new_device(dev, 1, &board_info);
|
||||
|
||||
return PTR_ERR_OR_ZERO(data->max17047);
|
||||
}
|
||||
|
||||
static int cht_int33fe_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct i2c_board_info board_info;
|
||||
struct cht_int33fe_data *data;
|
||||
struct i2c_client *max17047;
|
||||
struct fwnode_handle *fwnode;
|
||||
struct regulator *regulator;
|
||||
unsigned long long ptyp;
|
||||
acpi_status status;
|
||||
@@ -151,43 +358,25 @@ static int cht_int33fe_probe(struct platform_device *pdev)
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Work around BIOS bug, see comment on cht_int33fe_find_max17047 */
|
||||
max17047 = cht_int33fe_find_max17047();
|
||||
if (max17047) {
|
||||
/* Pre-existing i2c-client for the max17047, add device-props */
|
||||
ret = device_add_properties(&max17047->dev, max17047_props);
|
||||
if (ret)
|
||||
return ret;
|
||||
/* And re-probe to get the new device-props applied. */
|
||||
ret = device_reprobe(&max17047->dev);
|
||||
if (ret)
|
||||
dev_warn(dev, "Reprobing max17047 error: %d\n", ret);
|
||||
} else {
|
||||
memset(&board_info, 0, sizeof(board_info));
|
||||
strlcpy(board_info.type, "max17047", I2C_NAME_SIZE);
|
||||
board_info.dev_name = "max17047";
|
||||
board_info.properties = max17047_props;
|
||||
data->max17047 = i2c_acpi_new_device(dev, 1, &board_info);
|
||||
if (IS_ERR(data->max17047))
|
||||
return PTR_ERR(data->max17047);
|
||||
ret = cht_int33fe_add_nodes(data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Work around BIOS bug, see comment on cht_int33fe_check_for_max17047 */
|
||||
ret = cht_int33fe_register_max17047(dev, data);
|
||||
if (ret)
|
||||
goto out_remove_nodes;
|
||||
|
||||
fwnode = software_node_fwnode(&nodes[INT33FE_NODE_FUSB302]);
|
||||
if (!fwnode) {
|
||||
ret = -ENODEV;
|
||||
goto out_unregister_max17047;
|
||||
}
|
||||
|
||||
data->connections[0].endpoint[0] = "port0";
|
||||
data->connections[0].endpoint[1] = "i2c-pi3usb30532";
|
||||
data->connections[0].id = "orientation-switch";
|
||||
data->connections[1].endpoint[0] = "port0";
|
||||
data->connections[1].endpoint[1] = "i2c-pi3usb30532";
|
||||
data->connections[1].id = "mode-switch";
|
||||
data->connections[2].endpoint[0] = "i2c-fusb302";
|
||||
data->connections[2].endpoint[1] = "intel_xhci_usb_sw-role-switch";
|
||||
data->connections[2].id = "usb-role-switch";
|
||||
|
||||
device_connections_add(data->connections);
|
||||
|
||||
memset(&board_info, 0, sizeof(board_info));
|
||||
strlcpy(board_info.type, "typec_fusb302", I2C_NAME_SIZE);
|
||||
board_info.dev_name = "fusb302";
|
||||
board_info.properties = fusb302_props;
|
||||
board_info.fwnode = fwnode;
|
||||
board_info.irq = fusb302_irq;
|
||||
|
||||
data->fusb302 = i2c_acpi_new_device(dev, 2, &board_info);
|
||||
@@ -196,8 +385,15 @@ static int cht_int33fe_probe(struct platform_device *pdev)
|
||||
goto out_unregister_max17047;
|
||||
}
|
||||
|
||||
fwnode = software_node_fwnode(&nodes[INT33FE_NODE_PI3USB30532]);
|
||||
if (!fwnode) {
|
||||
ret = -ENODEV;
|
||||
goto out_unregister_fusb302;
|
||||
}
|
||||
|
||||
memset(&board_info, 0, sizeof(board_info));
|
||||
board_info.dev_name = "pi3usb30532";
|
||||
board_info.fwnode = fwnode;
|
||||
strlcpy(board_info.type, "pi3usb30532", I2C_NAME_SIZE);
|
||||
|
||||
data->pi3usb30532 = i2c_acpi_new_device(dev, 3, &board_info);
|
||||
@@ -216,7 +412,8 @@ out_unregister_fusb302:
|
||||
out_unregister_max17047:
|
||||
i2c_unregister_device(data->max17047);
|
||||
|
||||
device_connections_remove(data->connections);
|
||||
out_remove_nodes:
|
||||
cht_int33fe_remove_nodes(data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -229,7 +426,7 @@ static int cht_int33fe_remove(struct platform_device *pdev)
|
||||
i2c_unregister_device(data->fusb302);
|
||||
i2c_unregister_device(data->max17047);
|
||||
|
||||
device_connections_remove(data->connections);
|
||||
cht_int33fe_remove_nodes(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@@ -51,17 +51,6 @@
|
||||
#define GPE0A_STS_PORT 0x420
|
||||
#define GPE0A_EN_PORT 0x428
|
||||
|
||||
#define BAYTRAIL 0x01
|
||||
#define CHERRYTRAIL 0x02
|
||||
|
||||
#define ICPU(model, data) { X86_VENDOR_INTEL, 6, model, X86_FEATURE_ANY, data }
|
||||
|
||||
static const struct x86_cpu_id int0002_cpu_ids[] = {
|
||||
ICPU(INTEL_FAM6_ATOM_SILVERMONT, BAYTRAIL), /* Valleyview, Bay Trail */
|
||||
ICPU(INTEL_FAM6_ATOM_AIRMONT, CHERRYTRAIL), /* Braswell, Cherry Trail */
|
||||
{}
|
||||
};
|
||||
|
||||
/*
|
||||
* As this is not a real GPIO at all, but just a hack to model an event in
|
||||
* ACPI the get / set functions are dummy functions.
|
||||
@@ -157,6 +146,12 @@ static struct irq_chip int0002_cht_irqchip = {
|
||||
*/
|
||||
};
|
||||
|
||||
static const struct x86_cpu_id int0002_cpu_ids[] = {
|
||||
INTEL_CPU_FAM6(ATOM_SILVERMONT, int0002_byt_irqchip), /* Valleyview, Bay Trail */
|
||||
INTEL_CPU_FAM6(ATOM_AIRMONT, int0002_cht_irqchip), /* Braswell, Cherry Trail */
|
||||
{}
|
||||
};
|
||||
|
||||
static int int0002_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
@@ -210,10 +205,7 @@ static int int0002_probe(struct platform_device *pdev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (cpu_id->driver_data == BAYTRAIL)
|
||||
irq_chip = &int0002_byt_irqchip;
|
||||
else
|
||||
irq_chip = &int0002_cht_irqchip;
|
||||
irq_chip = (struct irq_chip *)cpu_id->driver_data;
|
||||
|
||||
ret = gpiochip_irqchip_add(chip, irq_chip, 0, handle_edge_irq,
|
||||
IRQ_TYPE_NONE);
|
||||
|
@@ -180,9 +180,13 @@ static int intel_menlow_memory_add(struct acpi_device *device)
|
||||
|
||||
static int intel_menlow_memory_remove(struct acpi_device *device)
|
||||
{
|
||||
struct thermal_cooling_device *cdev = acpi_driver_data(device);
|
||||
struct thermal_cooling_device *cdev;
|
||||
|
||||
if (!device || !cdev)
|
||||
if (!device)
|
||||
return -EINVAL;
|
||||
|
||||
cdev = acpi_driver_data(device);
|
||||
if (!cdev)
|
||||
return -EINVAL;
|
||||
|
||||
sysfs_remove_link(&device->dev.kobj, "thermal_cooling");
|
||||
|
@@ -26,6 +26,7 @@
|
||||
#include <asm/cpu_device_id.h>
|
||||
#include <asm/intel-family.h>
|
||||
#include <asm/msr.h>
|
||||
#include <asm/tsc.h>
|
||||
|
||||
#include "intel_pmc_core.h"
|
||||
|
||||
@@ -740,7 +741,9 @@ static int pmc_core_pkgc_show(struct seq_file *s, void *unused)
|
||||
if (rdmsrl_safe(map[index].bit_mask, &pcstate_count))
|
||||
continue;
|
||||
|
||||
seq_printf(s, "%-8s : 0x%llx\n", map[index].name,
|
||||
pcstate_count *= 1000;
|
||||
do_div(pcstate_count, tsc_khz);
|
||||
seq_printf(s, "%-8s : %llu\n", map[index].name,
|
||||
pcstate_count);
|
||||
}
|
||||
|
||||
@@ -753,14 +756,11 @@ static void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev)
|
||||
debugfs_remove_recursive(pmcdev->dbgfs_dir);
|
||||
}
|
||||
|
||||
static int pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
|
||||
static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
|
||||
{
|
||||
struct dentry *dir;
|
||||
|
||||
dir = debugfs_create_dir("pmc_core", NULL);
|
||||
if (!dir)
|
||||
return -ENOMEM;
|
||||
|
||||
pmcdev->dbgfs_dir = dir;
|
||||
|
||||
debugfs_create_file("slp_s0_residency_usec", 0444, dir, pmcdev,
|
||||
@@ -794,13 +794,10 @@ static int pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
|
||||
debugfs_create_bool("slp_s0_dbg_latch", 0644,
|
||||
dir, &slps0_dbg_latch);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static inline int pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
|
||||
static inline void pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev)
|
||||
@@ -815,6 +812,7 @@ static const struct x86_cpu_id intel_pmc_core_ids[] = {
|
||||
INTEL_CPU_FAM6(KABYLAKE_DESKTOP, spt_reg_map),
|
||||
INTEL_CPU_FAM6(CANNONLAKE_MOBILE, cnp_reg_map),
|
||||
INTEL_CPU_FAM6(ICELAKE_MOBILE, icl_reg_map),
|
||||
INTEL_CPU_FAM6(ICELAKE_NNPI, icl_reg_map),
|
||||
{}
|
||||
};
|
||||
|
||||
@@ -862,7 +860,6 @@ static int pmc_core_probe(struct platform_device *pdev)
|
||||
struct pmc_dev *pmcdev = &pmc;
|
||||
const struct x86_cpu_id *cpu_id;
|
||||
u64 slp_s0_addr;
|
||||
int err;
|
||||
|
||||
if (device_initialized)
|
||||
return -ENODEV;
|
||||
@@ -896,12 +893,7 @@ static int pmc_core_probe(struct platform_device *pdev)
|
||||
pmcdev->pmc_xram_read_bit = pmc_core_check_read_lock_bit();
|
||||
dmi_check_system(pmc_core_dmi_table);
|
||||
|
||||
err = pmc_core_dbgfs_register(pmcdev);
|
||||
if (err < 0) {
|
||||
dev_warn(&pdev->dev, "debugfs register failed.\n");
|
||||
iounmap(pmcdev->regbase);
|
||||
return err;
|
||||
}
|
||||
pmc_core_dbgfs_register(pmcdev);
|
||||
|
||||
device_initialized = true;
|
||||
dev_info(&pdev->dev, " initialized\n");
|
||||
@@ -1023,47 +1015,23 @@ static const struct dev_pm_ops pmc_core_pm_ops = {
|
||||
SET_LATE_SYSTEM_SLEEP_PM_OPS(pmc_core_suspend, pmc_core_resume)
|
||||
};
|
||||
|
||||
static const struct acpi_device_id pmc_core_acpi_ids[] = {
|
||||
{"INT33A1", 0}, /* _HID for Intel Power Engine, _CID PNP0D80*/
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, pmc_core_acpi_ids);
|
||||
|
||||
static struct platform_driver pmc_core_driver = {
|
||||
.driver = {
|
||||
.name = "intel_pmc_core",
|
||||
.acpi_match_table = ACPI_PTR(pmc_core_acpi_ids),
|
||||
.pm = &pmc_core_pm_ops,
|
||||
},
|
||||
.probe = pmc_core_probe,
|
||||
.remove = pmc_core_remove,
|
||||
};
|
||||
|
||||
static struct platform_device pmc_core_device = {
|
||||
.name = "intel_pmc_core",
|
||||
};
|
||||
|
||||
static int __init pmc_core_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!x86_match_cpu(intel_pmc_core_ids))
|
||||
return -ENODEV;
|
||||
|
||||
ret = platform_driver_register(&pmc_core_driver);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = platform_device_register(&pmc_core_device);
|
||||
if (ret) {
|
||||
platform_driver_unregister(&pmc_core_driver);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit pmc_core_exit(void)
|
||||
{
|
||||
platform_device_unregister(&pmc_core_device);
|
||||
platform_driver_unregister(&pmc_core_driver);
|
||||
}
|
||||
|
||||
module_init(pmc_core_init)
|
||||
module_exit(pmc_core_exit)
|
||||
module_platform_driver(pmc_core_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Intel PMC Core Driver");
|
||||
|
62
drivers/platform/x86/intel_pmc_core_pltdrv.c
Arquivo normal
62
drivers/platform/x86/intel_pmc_core_pltdrv.c
Arquivo normal
@@ -0,0 +1,62 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/*
|
||||
* Intel PMC Core platform init
|
||||
* Copyright (c) 2019, Google Inc.
|
||||
* Author - Rajat Jain
|
||||
*
|
||||
* This code instantiates platform devices for intel_pmc_core driver, only
|
||||
* on supported platforms that may not have the ACPI devices in the ACPI tables.
|
||||
* No new platforms should be added here, because we expect that new platforms
|
||||
* should all have the ACPI device, which is the preferred way of enumeration.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <asm/cpu_device_id.h>
|
||||
#include <asm/intel-family.h>
|
||||
|
||||
static struct platform_device pmc_core_device = {
|
||||
.name = "intel_pmc_core",
|
||||
};
|
||||
|
||||
/*
|
||||
* intel_pmc_core_platform_ids is the list of platforms where we want to
|
||||
* instantiate the platform_device if not already instantiated. This is
|
||||
* different than intel_pmc_core_ids in intel_pmc_core.c which is the
|
||||
* list of platforms that the driver supports for pmc_core device. The
|
||||
* other list may grow, but this list should not.
|
||||
*/
|
||||
static const struct x86_cpu_id intel_pmc_core_platform_ids[] = {
|
||||
INTEL_CPU_FAM6(SKYLAKE_MOBILE, pmc_core_device),
|
||||
INTEL_CPU_FAM6(SKYLAKE_DESKTOP, pmc_core_device),
|
||||
INTEL_CPU_FAM6(KABYLAKE_MOBILE, pmc_core_device),
|
||||
INTEL_CPU_FAM6(KABYLAKE_DESKTOP, pmc_core_device),
|
||||
INTEL_CPU_FAM6(CANNONLAKE_MOBILE, pmc_core_device),
|
||||
INTEL_CPU_FAM6(ICELAKE_MOBILE, pmc_core_device),
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(x86cpu, intel_pmc_core_platform_ids);
|
||||
|
||||
static int __init pmc_core_platform_init(void)
|
||||
{
|
||||
/* Skip creating the platform device if ACPI already has a device */
|
||||
if (acpi_dev_present("INT33A1", NULL, -1))
|
||||
return -ENODEV;
|
||||
|
||||
if (!x86_match_cpu(intel_pmc_core_platform_ids))
|
||||
return -ENODEV;
|
||||
|
||||
return platform_device_register(&pmc_core_device);
|
||||
}
|
||||
|
||||
static void __exit pmc_core_platform_exit(void)
|
||||
{
|
||||
platform_device_unregister(&pmc_core_device);
|
||||
}
|
||||
|
||||
module_init(pmc_core_platform_init);
|
||||
module_exit(pmc_core_platform_exit);
|
||||
MODULE_LICENSE("GPL v2");
|
17
drivers/platform/x86/intel_speed_select_if/Kconfig
Arquivo normal
17
drivers/platform/x86/intel_speed_select_if/Kconfig
Arquivo normal
@@ -0,0 +1,17 @@
|
||||
menu "Intel Speed Select Technology interface support"
|
||||
depends on PCI
|
||||
depends on X86_64 || COMPILE_TEST
|
||||
|
||||
config INTEL_SPEED_SELECT_INTERFACE
|
||||
tristate "Intel(R) Speed Select Technology interface drivers"
|
||||
help
|
||||
This config enables the Intel(R) Speed Select Technology interface
|
||||
drivers. The Intel(R) speed select technology features are non
|
||||
architectural and only supported on specific Xeon(R) servers.
|
||||
These drivers provide interface to directly communicate with hardware
|
||||
via MMIO and Mail boxes to enumerate and control all the speed select
|
||||
features.
|
||||
|
||||
Enable this config, if there is a need to enable and control the
|
||||
Intel(R) Speed Select Technology features from the user space.
|
||||
endmenu
|
10
drivers/platform/x86/intel_speed_select_if/Makefile
Arquivo normal
10
drivers/platform/x86/intel_speed_select_if/Makefile
Arquivo normal
@@ -0,0 +1,10 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Makefile - Intel Speed Select Interface drivers
|
||||
# Copyright (c) 2019, Intel Corporation.
|
||||
#
|
||||
|
||||
obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_common.o
|
||||
obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mmio.o
|
||||
obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mbox_pci.o
|
||||
obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mbox_msr.o
|
672
drivers/platform/x86/intel_speed_select_if/isst_if_common.c
Arquivo normal
672
drivers/platform/x86/intel_speed_select_if/isst_if_common.c
Arquivo normal
@@ -0,0 +1,672 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Intel Speed Select Interface: Common functions
|
||||
* Copyright (c) 2019, Intel Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/cpufeature.h>
|
||||
#include <linux/cpuhotplug.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/hashtable.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/sched/signal.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <uapi/linux/isst_if.h>
|
||||
|
||||
#include "isst_if_common.h"
|
||||
|
||||
#define MSR_THREAD_ID_INFO 0x53
|
||||
#define MSR_CPU_BUS_NUMBER 0x128
|
||||
|
||||
static struct isst_if_cmd_cb punit_callbacks[ISST_IF_DEV_MAX];
|
||||
|
||||
static int punit_msr_white_list[] = {
|
||||
MSR_TURBO_RATIO_LIMIT,
|
||||
MSR_CONFIG_TDP_CONTROL,
|
||||
};
|
||||
|
||||
struct isst_valid_cmd_ranges {
|
||||
u16 cmd;
|
||||
u16 sub_cmd_beg;
|
||||
u16 sub_cmd_end;
|
||||
};
|
||||
|
||||
struct isst_cmd_set_req_type {
|
||||
u16 cmd;
|
||||
u16 sub_cmd;
|
||||
u16 param;
|
||||
};
|
||||
|
||||
static const struct isst_valid_cmd_ranges isst_valid_cmds[] = {
|
||||
{0xD0, 0x00, 0x03},
|
||||
{0x7F, 0x00, 0x0B},
|
||||
{0x7F, 0x10, 0x12},
|
||||
{0x7F, 0x20, 0x23},
|
||||
};
|
||||
|
||||
static const struct isst_cmd_set_req_type isst_cmd_set_reqs[] = {
|
||||
{0xD0, 0x00, 0x08},
|
||||
{0xD0, 0x01, 0x08},
|
||||
{0xD0, 0x02, 0x08},
|
||||
{0xD0, 0x03, 0x08},
|
||||
{0x7F, 0x02, 0x00},
|
||||
{0x7F, 0x08, 0x00},
|
||||
};
|
||||
|
||||
struct isst_cmd {
|
||||
struct hlist_node hnode;
|
||||
u64 data;
|
||||
u32 cmd;
|
||||
int cpu;
|
||||
int mbox_cmd_type;
|
||||
u32 param;
|
||||
};
|
||||
|
||||
static DECLARE_HASHTABLE(isst_hash, 8);
|
||||
static DEFINE_MUTEX(isst_hash_lock);
|
||||
|
||||
static int isst_store_new_cmd(int cmd, u32 cpu, int mbox_cmd_type, u32 param,
|
||||
u32 data)
|
||||
{
|
||||
struct isst_cmd *sst_cmd;
|
||||
|
||||
sst_cmd = kmalloc(sizeof(*sst_cmd), GFP_KERNEL);
|
||||
if (!sst_cmd)
|
||||
return -ENOMEM;
|
||||
|
||||
sst_cmd->cpu = cpu;
|
||||
sst_cmd->cmd = cmd;
|
||||
sst_cmd->mbox_cmd_type = mbox_cmd_type;
|
||||
sst_cmd->param = param;
|
||||
sst_cmd->data = data;
|
||||
|
||||
hash_add(isst_hash, &sst_cmd->hnode, sst_cmd->cmd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void isst_delete_hash(void)
|
||||
{
|
||||
struct isst_cmd *sst_cmd;
|
||||
struct hlist_node *tmp;
|
||||
int i;
|
||||
|
||||
hash_for_each_safe(isst_hash, i, tmp, sst_cmd, hnode) {
|
||||
hash_del(&sst_cmd->hnode);
|
||||
kfree(sst_cmd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* isst_store_cmd() - Store command to a hash table
|
||||
* @cmd: Mailbox command.
|
||||
* @sub_cmd: Mailbox sub-command or MSR id.
|
||||
* @mbox_cmd_type: Mailbox or MSR command.
|
||||
* @param: Mailbox parameter.
|
||||
* @data: Mailbox request data or MSR data.
|
||||
*
|
||||
* Stores the command to a hash table if there is no such command already
|
||||
* stored. If already stored update the latest parameter and data for the
|
||||
* command.
|
||||
*
|
||||
* Return: Return result of store to hash table, 0 for success, others for
|
||||
* failure.
|
||||
*/
|
||||
int isst_store_cmd(int cmd, int sub_cmd, u32 cpu, int mbox_cmd_type,
|
||||
u32 param, u64 data)
|
||||
{
|
||||
struct isst_cmd *sst_cmd;
|
||||
int full_cmd, ret;
|
||||
|
||||
full_cmd = (cmd & GENMASK_ULL(15, 0)) << 16;
|
||||
full_cmd |= (sub_cmd & GENMASK_ULL(15, 0));
|
||||
mutex_lock(&isst_hash_lock);
|
||||
hash_for_each_possible(isst_hash, sst_cmd, hnode, full_cmd) {
|
||||
if (sst_cmd->cmd == full_cmd && sst_cmd->cpu == cpu &&
|
||||
sst_cmd->mbox_cmd_type == mbox_cmd_type) {
|
||||
sst_cmd->param = param;
|
||||
sst_cmd->data = data;
|
||||
mutex_unlock(&isst_hash_lock);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
ret = isst_store_new_cmd(full_cmd, cpu, mbox_cmd_type, param, data);
|
||||
mutex_unlock(&isst_hash_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(isst_store_cmd);
|
||||
|
||||
static void isst_mbox_resume_command(struct isst_if_cmd_cb *cb,
|
||||
struct isst_cmd *sst_cmd)
|
||||
{
|
||||
struct isst_if_mbox_cmd mbox_cmd;
|
||||
int wr_only;
|
||||
|
||||
mbox_cmd.command = (sst_cmd->cmd & GENMASK_ULL(31, 16)) >> 16;
|
||||
mbox_cmd.sub_command = sst_cmd->cmd & GENMASK_ULL(15, 0);
|
||||
mbox_cmd.parameter = sst_cmd->param;
|
||||
mbox_cmd.req_data = sst_cmd->data;
|
||||
mbox_cmd.logical_cpu = sst_cmd->cpu;
|
||||
(cb->cmd_callback)((u8 *)&mbox_cmd, &wr_only, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* isst_resume_common() - Process Resume request
|
||||
*
|
||||
* On resume replay all mailbox commands and MSRs.
|
||||
*
|
||||
* Return: None.
|
||||
*/
|
||||
void isst_resume_common(void)
|
||||
{
|
||||
struct isst_cmd *sst_cmd;
|
||||
int i;
|
||||
|
||||
hash_for_each(isst_hash, i, sst_cmd, hnode) {
|
||||
struct isst_if_cmd_cb *cb;
|
||||
|
||||
if (sst_cmd->mbox_cmd_type) {
|
||||
cb = &punit_callbacks[ISST_IF_DEV_MBOX];
|
||||
if (cb->registered)
|
||||
isst_mbox_resume_command(cb, sst_cmd);
|
||||
} else {
|
||||
wrmsrl_safe_on_cpu(sst_cmd->cpu, sst_cmd->cmd,
|
||||
sst_cmd->data);
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(isst_resume_common);
|
||||
|
||||
static void isst_restore_msr_local(int cpu)
|
||||
{
|
||||
struct isst_cmd *sst_cmd;
|
||||
int i;
|
||||
|
||||
mutex_lock(&isst_hash_lock);
|
||||
for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) {
|
||||
if (!punit_msr_white_list[i])
|
||||
break;
|
||||
|
||||
hash_for_each_possible(isst_hash, sst_cmd, hnode,
|
||||
punit_msr_white_list[i]) {
|
||||
if (!sst_cmd->mbox_cmd_type && sst_cmd->cpu == cpu)
|
||||
wrmsrl_safe(sst_cmd->cmd, sst_cmd->data);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&isst_hash_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* isst_if_mbox_cmd_invalid() - Check invalid mailbox commands
|
||||
* @cmd: Pointer to the command structure to verify.
|
||||
*
|
||||
* Invalid command to PUNIT to may result in instability of the platform.
|
||||
* This function has a whitelist of commands, which are allowed.
|
||||
*
|
||||
* Return: Return true if the command is invalid, else false.
|
||||
*/
|
||||
bool isst_if_mbox_cmd_invalid(struct isst_if_mbox_cmd *cmd)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (cmd->logical_cpu >= nr_cpu_ids)
|
||||
return true;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(isst_valid_cmds); ++i) {
|
||||
if (cmd->command == isst_valid_cmds[i].cmd &&
|
||||
(cmd->sub_command >= isst_valid_cmds[i].sub_cmd_beg &&
|
||||
cmd->sub_command <= isst_valid_cmds[i].sub_cmd_end)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(isst_if_mbox_cmd_invalid);
|
||||
|
||||
/**
|
||||
* isst_if_mbox_cmd_set_req() - Check mailbox command is a set request
|
||||
* @cmd: Pointer to the command structure to verify.
|
||||
*
|
||||
* Check if the given mail box level is set request and not a get request.
|
||||
*
|
||||
* Return: Return true if the command is set_req, else false.
|
||||
*/
|
||||
bool isst_if_mbox_cmd_set_req(struct isst_if_mbox_cmd *cmd)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(isst_cmd_set_reqs); ++i) {
|
||||
if (cmd->command == isst_cmd_set_reqs[i].cmd &&
|
||||
cmd->sub_command == isst_cmd_set_reqs[i].sub_cmd &&
|
||||
cmd->parameter == isst_cmd_set_reqs[i].param) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(isst_if_mbox_cmd_set_req);
|
||||
|
||||
static int isst_if_get_platform_info(void __user *argp)
|
||||
{
|
||||
struct isst_if_platform_info info;
|
||||
|
||||
info.api_version = ISST_IF_API_VERSION,
|
||||
info.driver_version = ISST_IF_DRIVER_VERSION,
|
||||
info.max_cmds_per_ioctl = ISST_IF_CMD_LIMIT,
|
||||
info.mbox_supported = punit_callbacks[ISST_IF_DEV_MBOX].registered;
|
||||
info.mmio_supported = punit_callbacks[ISST_IF_DEV_MMIO].registered;
|
||||
|
||||
if (copy_to_user(argp, &info, sizeof(info)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
struct isst_if_cpu_info {
|
||||
/* For BUS 0 and BUS 1 only, which we need for PUNIT interface */
|
||||
int bus_info[2];
|
||||
int punit_cpu_id;
|
||||
};
|
||||
|
||||
static struct isst_if_cpu_info *isst_cpu_info;
|
||||
|
||||
/**
|
||||
* isst_if_get_pci_dev() - Get the PCI device instance for a CPU
|
||||
* @cpu: Logical CPU number.
|
||||
* @bus_number: The bus number assigned by the hardware.
|
||||
* @dev: The device number assigned by the hardware.
|
||||
* @fn: The function number assigned by the hardware.
|
||||
*
|
||||
* Using cached bus information, find out the PCI device for a bus number,
|
||||
* device and function.
|
||||
*
|
||||
* Return: Return pci_dev pointer or NULL.
|
||||
*/
|
||||
struct pci_dev *isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn)
|
||||
{
|
||||
int bus_number;
|
||||
|
||||
if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids ||
|
||||
cpu >= num_possible_cpus())
|
||||
return NULL;
|
||||
|
||||
bus_number = isst_cpu_info[cpu].bus_info[bus_no];
|
||||
if (bus_number < 0)
|
||||
return NULL;
|
||||
|
||||
return pci_get_domain_bus_and_slot(0, bus_number, PCI_DEVFN(dev, fn));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(isst_if_get_pci_dev);
|
||||
|
||||
static int isst_if_cpu_online(unsigned int cpu)
|
||||
{
|
||||
u64 data;
|
||||
int ret;
|
||||
|
||||
ret = rdmsrl_safe(MSR_CPU_BUS_NUMBER, &data);
|
||||
if (ret) {
|
||||
/* This is not a fatal error on MSR mailbox only I/F */
|
||||
isst_cpu_info[cpu].bus_info[0] = -1;
|
||||
isst_cpu_info[cpu].bus_info[1] = -1;
|
||||
} else {
|
||||
isst_cpu_info[cpu].bus_info[0] = data & 0xff;
|
||||
isst_cpu_info[cpu].bus_info[1] = (data >> 8) & 0xff;
|
||||
}
|
||||
|
||||
ret = rdmsrl_safe(MSR_THREAD_ID_INFO, &data);
|
||||
if (ret) {
|
||||
isst_cpu_info[cpu].punit_cpu_id = -1;
|
||||
return ret;
|
||||
}
|
||||
isst_cpu_info[cpu].punit_cpu_id = data;
|
||||
|
||||
isst_restore_msr_local(cpu);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isst_if_online_id;
|
||||
|
||||
static int isst_if_cpu_info_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
isst_cpu_info = kcalloc(num_possible_cpus(),
|
||||
sizeof(*isst_cpu_info),
|
||||
GFP_KERNEL);
|
||||
if (!isst_cpu_info)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
|
||||
"platform/x86/isst-if:online",
|
||||
isst_if_cpu_online, NULL);
|
||||
if (ret < 0) {
|
||||
kfree(isst_cpu_info);
|
||||
return ret;
|
||||
}
|
||||
|
||||
isst_if_online_id = ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void isst_if_cpu_info_exit(void)
|
||||
{
|
||||
cpuhp_remove_state(isst_if_online_id);
|
||||
kfree(isst_cpu_info);
|
||||
};
|
||||
|
||||
static long isst_if_proc_phyid_req(u8 *cmd_ptr, int *write_only, int resume)
|
||||
{
|
||||
struct isst_if_cpu_map *cpu_map;
|
||||
|
||||
cpu_map = (struct isst_if_cpu_map *)cmd_ptr;
|
||||
if (cpu_map->logical_cpu >= nr_cpu_ids ||
|
||||
cpu_map->logical_cpu >= num_possible_cpus())
|
||||
return -EINVAL;
|
||||
|
||||
*write_only = 0;
|
||||
cpu_map->physical_cpu = isst_cpu_info[cpu_map->logical_cpu].punit_cpu_id;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool match_punit_msr_white_list(int msr)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) {
|
||||
if (punit_msr_white_list[i] == msr)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static long isst_if_msr_cmd_req(u8 *cmd_ptr, int *write_only, int resume)
|
||||
{
|
||||
struct isst_if_msr_cmd *msr_cmd;
|
||||
int ret;
|
||||
|
||||
msr_cmd = (struct isst_if_msr_cmd *)cmd_ptr;
|
||||
|
||||
if (!match_punit_msr_white_list(msr_cmd->msr))
|
||||
return -EINVAL;
|
||||
|
||||
if (msr_cmd->logical_cpu >= nr_cpu_ids)
|
||||
return -EINVAL;
|
||||
|
||||
if (msr_cmd->read_write) {
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
ret = wrmsrl_safe_on_cpu(msr_cmd->logical_cpu,
|
||||
msr_cmd->msr,
|
||||
msr_cmd->data);
|
||||
*write_only = 1;
|
||||
if (!ret && !resume)
|
||||
ret = isst_store_cmd(0, msr_cmd->msr,
|
||||
msr_cmd->logical_cpu,
|
||||
0, 0, msr_cmd->data);
|
||||
} else {
|
||||
u64 data;
|
||||
|
||||
ret = rdmsrl_safe_on_cpu(msr_cmd->logical_cpu,
|
||||
msr_cmd->msr, &data);
|
||||
if (!ret) {
|
||||
msr_cmd->data = data;
|
||||
*write_only = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long isst_if_exec_multi_cmd(void __user *argp, struct isst_if_cmd_cb *cb)
|
||||
{
|
||||
unsigned char __user *ptr;
|
||||
u32 cmd_count;
|
||||
u8 *cmd_ptr;
|
||||
long ret;
|
||||
int i;
|
||||
|
||||
/* Each multi command has u32 command count as the first field */
|
||||
if (copy_from_user(&cmd_count, argp, sizeof(cmd_count)))
|
||||
return -EFAULT;
|
||||
|
||||
if (!cmd_count || cmd_count > ISST_IF_CMD_LIMIT)
|
||||
return -EINVAL;
|
||||
|
||||
cmd_ptr = kmalloc(cb->cmd_size, GFP_KERNEL);
|
||||
if (!cmd_ptr)
|
||||
return -ENOMEM;
|
||||
|
||||
/* cb->offset points to start of the command after the command count */
|
||||
ptr = argp + cb->offset;
|
||||
|
||||
for (i = 0; i < cmd_count; ++i) {
|
||||
int wr_only;
|
||||
|
||||
if (signal_pending(current)) {
|
||||
ret = -EINTR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (copy_from_user(cmd_ptr, ptr, cb->cmd_size)) {
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
ret = cb->cmd_callback(cmd_ptr, &wr_only, 0);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
if (!wr_only && copy_to_user(ptr, cmd_ptr, cb->cmd_size)) {
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
ptr += cb->cmd_size;
|
||||
}
|
||||
|
||||
kfree(cmd_ptr);
|
||||
|
||||
return i ? i : ret;
|
||||
}
|
||||
|
||||
static long isst_if_def_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
void __user *argp = (void __user *)arg;
|
||||
struct isst_if_cmd_cb cmd_cb;
|
||||
struct isst_if_cmd_cb *cb;
|
||||
long ret = -ENOTTY;
|
||||
|
||||
switch (cmd) {
|
||||
case ISST_IF_GET_PLATFORM_INFO:
|
||||
ret = isst_if_get_platform_info(argp);
|
||||
break;
|
||||
case ISST_IF_GET_PHY_ID:
|
||||
cmd_cb.cmd_size = sizeof(struct isst_if_cpu_map);
|
||||
cmd_cb.offset = offsetof(struct isst_if_cpu_maps, cpu_map);
|
||||
cmd_cb.cmd_callback = isst_if_proc_phyid_req;
|
||||
ret = isst_if_exec_multi_cmd(argp, &cmd_cb);
|
||||
break;
|
||||
case ISST_IF_IO_CMD:
|
||||
cb = &punit_callbacks[ISST_IF_DEV_MMIO];
|
||||
if (cb->registered)
|
||||
ret = isst_if_exec_multi_cmd(argp, cb);
|
||||
break;
|
||||
case ISST_IF_MBOX_COMMAND:
|
||||
cb = &punit_callbacks[ISST_IF_DEV_MBOX];
|
||||
if (cb->registered)
|
||||
ret = isst_if_exec_multi_cmd(argp, cb);
|
||||
break;
|
||||
case ISST_IF_MSR_COMMAND:
|
||||
cmd_cb.cmd_size = sizeof(struct isst_if_msr_cmd);
|
||||
cmd_cb.offset = offsetof(struct isst_if_msr_cmds, msr_cmd);
|
||||
cmd_cb.cmd_callback = isst_if_msr_cmd_req;
|
||||
ret = isst_if_exec_multi_cmd(argp, &cmd_cb);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static DEFINE_MUTEX(punit_misc_dev_lock);
|
||||
static int misc_usage_count;
|
||||
static int misc_device_ret;
|
||||
static int misc_device_open;
|
||||
|
||||
static int isst_if_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
int i, ret = 0;
|
||||
|
||||
/* Fail open, if a module is going away */
|
||||
mutex_lock(&punit_misc_dev_lock);
|
||||
for (i = 0; i < ISST_IF_DEV_MAX; ++i) {
|
||||
struct isst_if_cmd_cb *cb = &punit_callbacks[i];
|
||||
|
||||
if (cb->registered && !try_module_get(cb->owner)) {
|
||||
ret = -ENODEV;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ret) {
|
||||
int j;
|
||||
|
||||
for (j = 0; j < i; ++j) {
|
||||
struct isst_if_cmd_cb *cb;
|
||||
|
||||
cb = &punit_callbacks[j];
|
||||
if (cb->registered)
|
||||
module_put(cb->owner);
|
||||
}
|
||||
} else {
|
||||
misc_device_open++;
|
||||
}
|
||||
mutex_unlock(&punit_misc_dev_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int isst_if_relase(struct inode *inode, struct file *f)
|
||||
{
|
||||
int i;
|
||||
|
||||
mutex_lock(&punit_misc_dev_lock);
|
||||
misc_device_open--;
|
||||
for (i = 0; i < ISST_IF_DEV_MAX; ++i) {
|
||||
struct isst_if_cmd_cb *cb = &punit_callbacks[i];
|
||||
|
||||
if (cb->registered)
|
||||
module_put(cb->owner);
|
||||
}
|
||||
mutex_unlock(&punit_misc_dev_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations isst_if_char_driver_ops = {
|
||||
.open = isst_if_open,
|
||||
.unlocked_ioctl = isst_if_def_ioctl,
|
||||
.release = isst_if_relase,
|
||||
};
|
||||
|
||||
static struct miscdevice isst_if_char_driver = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "isst_interface",
|
||||
.fops = &isst_if_char_driver_ops,
|
||||
};
|
||||
|
||||
/**
|
||||
* isst_if_cdev_register() - Register callback for IOCTL
|
||||
* @device_type: The device type this callback handling.
|
||||
* @cb: Callback structure.
|
||||
*
|
||||
* This function registers a callback to device type. On very first call
|
||||
* it will register a misc device, which is used for user kernel interface.
|
||||
* Other calls simply increment ref count. Registry will fail, if the user
|
||||
* already opened misc device for operation. Also if the misc device
|
||||
* creation failed, then it will not try again and all callers will get
|
||||
* failure code.
|
||||
*
|
||||
* Return: Return the return value from the misc creation device or -EINVAL
|
||||
* for unsupported device type.
|
||||
*/
|
||||
int isst_if_cdev_register(int device_type, struct isst_if_cmd_cb *cb)
|
||||
{
|
||||
if (misc_device_ret)
|
||||
return misc_device_ret;
|
||||
|
||||
if (device_type >= ISST_IF_DEV_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&punit_misc_dev_lock);
|
||||
if (misc_device_open) {
|
||||
mutex_unlock(&punit_misc_dev_lock);
|
||||
return -EAGAIN;
|
||||
}
|
||||
if (!misc_usage_count) {
|
||||
int ret;
|
||||
|
||||
misc_device_ret = misc_register(&isst_if_char_driver);
|
||||
if (misc_device_ret)
|
||||
goto unlock_exit;
|
||||
|
||||
ret = isst_if_cpu_info_init();
|
||||
if (ret) {
|
||||
misc_deregister(&isst_if_char_driver);
|
||||
misc_device_ret = ret;
|
||||
goto unlock_exit;
|
||||
}
|
||||
}
|
||||
memcpy(&punit_callbacks[device_type], cb, sizeof(*cb));
|
||||
punit_callbacks[device_type].registered = 1;
|
||||
misc_usage_count++;
|
||||
unlock_exit:
|
||||
mutex_unlock(&punit_misc_dev_lock);
|
||||
|
||||
return misc_device_ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(isst_if_cdev_register);
|
||||
|
||||
/**
|
||||
* isst_if_cdev_unregister() - Unregister callback for IOCTL
|
||||
* @device_type: The device type to unregister.
|
||||
*
|
||||
* This function unregisters the previously registered callback. If this
|
||||
* is the last callback unregistering, then misc device is removed.
|
||||
*
|
||||
* Return: None.
|
||||
*/
|
||||
void isst_if_cdev_unregister(int device_type)
|
||||
{
|
||||
mutex_lock(&punit_misc_dev_lock);
|
||||
misc_usage_count--;
|
||||
punit_callbacks[device_type].registered = 0;
|
||||
if (device_type == ISST_IF_DEV_MBOX)
|
||||
isst_delete_hash();
|
||||
if (!misc_usage_count && !misc_device_ret) {
|
||||
misc_deregister(&isst_if_char_driver);
|
||||
isst_if_cpu_info_exit();
|
||||
}
|
||||
mutex_unlock(&punit_misc_dev_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(isst_if_cdev_unregister);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
69
drivers/platform/x86/intel_speed_select_if/isst_if_common.h
Arquivo normal
69
drivers/platform/x86/intel_speed_select_if/isst_if_common.h
Arquivo normal
@@ -0,0 +1,69 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Intel Speed Select Interface: Drivers Internal defines
|
||||
* Copyright (c) 2019, Intel Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
|
||||
*/
|
||||
|
||||
#ifndef __ISST_IF_COMMON_H
|
||||
#define __ISST_IF_COMMON_H
|
||||
|
||||
#define INTEL_RAPL_PRIO_DEVID_0 0x3451
|
||||
#define INTEL_CFG_MBOX_DEVID_0 0x3459
|
||||
|
||||
/*
|
||||
* Validate maximum commands in a single request.
|
||||
* This is enough to handle command to every core in one ioctl, or all
|
||||
* possible message id to one CPU. Limit is also helpful for resonse time
|
||||
* per IOCTL request, as PUNIT may take different times to process each
|
||||
* request and may hold for long for too many commands.
|
||||
*/
|
||||
#define ISST_IF_CMD_LIMIT 64
|
||||
|
||||
#define ISST_IF_API_VERSION 0x01
|
||||
#define ISST_IF_DRIVER_VERSION 0x01
|
||||
|
||||
#define ISST_IF_DEV_MBOX 0
|
||||
#define ISST_IF_DEV_MMIO 1
|
||||
#define ISST_IF_DEV_MAX 2
|
||||
|
||||
/**
|
||||
* struct isst_if_cmd_cb - Used to register a IOCTL handler
|
||||
* @registered: Used by the common code to store registry. Caller don't
|
||||
* to touch this field
|
||||
* @cmd_size: The command size of the individual command in IOCTL
|
||||
* @offset: Offset to the first valid member in command structure.
|
||||
* This will be the offset of the start of the command
|
||||
* after command count field
|
||||
* @cmd_callback: Callback function to handle IOCTL. The callback has the
|
||||
* command pointer with data for command. There is a pointer
|
||||
* called write_only, which when set, will not copy the
|
||||
* response to user ioctl buffer. The "resume" argument
|
||||
* can be used to avoid storing the command for replay
|
||||
* during system resume
|
||||
*
|
||||
* This structure is used to register an handler for IOCTL. To avoid
|
||||
* code duplication common code handles all the IOCTL command read/write
|
||||
* including handling multiple command in single IOCTL. The caller just
|
||||
* need to execute a command via the registered callback.
|
||||
*/
|
||||
struct isst_if_cmd_cb {
|
||||
int registered;
|
||||
int cmd_size;
|
||||
int offset;
|
||||
struct module *owner;
|
||||
long (*cmd_callback)(u8 *ptr, int *write_only, int resume);
|
||||
};
|
||||
|
||||
/* Internal interface functions */
|
||||
int isst_if_cdev_register(int type, struct isst_if_cmd_cb *cb);
|
||||
void isst_if_cdev_unregister(int type);
|
||||
struct pci_dev *isst_if_get_pci_dev(int cpu, int bus, int dev, int fn);
|
||||
bool isst_if_mbox_cmd_set_req(struct isst_if_mbox_cmd *mbox_cmd);
|
||||
bool isst_if_mbox_cmd_invalid(struct isst_if_mbox_cmd *cmd);
|
||||
int isst_store_cmd(int cmd, int sub_command, u32 cpu, int mbox_cmd,
|
||||
u32 param, u64 data);
|
||||
void isst_resume_common(void);
|
||||
#endif
|
216
drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c
Arquivo normal
216
drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c
Arquivo normal
@@ -0,0 +1,216 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Intel Speed Select Interface: Mbox via MSR Interface
|
||||
* Copyright (c) 2019, Intel Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/cpuhotplug.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/sched/signal.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/topology.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <uapi/linux/isst_if.h>
|
||||
#include <asm/cpu_device_id.h>
|
||||
#include <asm/intel-family.h>
|
||||
|
||||
#include "isst_if_common.h"
|
||||
|
||||
#define MSR_OS_MAILBOX_INTERFACE 0xB0
|
||||
#define MSR_OS_MAILBOX_DATA 0xB1
|
||||
#define MSR_OS_MAILBOX_BUSY_BIT 31
|
||||
|
||||
/*
|
||||
* Based on experiments count is never more than 1, as the MSR overhead
|
||||
* is enough to finish the command. So here this is the worst case number.
|
||||
*/
|
||||
#define OS_MAILBOX_RETRY_COUNT 3
|
||||
|
||||
static int isst_if_send_mbox_cmd(u8 command, u8 sub_command, u32 parameter,
|
||||
u32 command_data, u32 *response_data)
|
||||
{
|
||||
u32 retries;
|
||||
u64 data;
|
||||
int ret;
|
||||
|
||||
/* Poll for rb bit == 0 */
|
||||
retries = OS_MAILBOX_RETRY_COUNT;
|
||||
do {
|
||||
rdmsrl(MSR_OS_MAILBOX_INTERFACE, data);
|
||||
if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) {
|
||||
ret = -EBUSY;
|
||||
continue;
|
||||
}
|
||||
ret = 0;
|
||||
break;
|
||||
} while (--retries);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Write DATA register */
|
||||
wrmsrl(MSR_OS_MAILBOX_DATA, command_data);
|
||||
|
||||
/* Write command register */
|
||||
data = BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT) |
|
||||
(parameter & GENMASK_ULL(13, 0)) << 16 |
|
||||
(sub_command << 8) |
|
||||
command;
|
||||
wrmsrl(MSR_OS_MAILBOX_INTERFACE, data);
|
||||
|
||||
/* Poll for rb bit == 0 */
|
||||
retries = OS_MAILBOX_RETRY_COUNT;
|
||||
do {
|
||||
rdmsrl(MSR_OS_MAILBOX_INTERFACE, data);
|
||||
if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) {
|
||||
ret = -EBUSY;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data & 0xff)
|
||||
return -ENXIO;
|
||||
|
||||
if (response_data) {
|
||||
rdmsrl(MSR_OS_MAILBOX_DATA, data);
|
||||
*response_data = data;
|
||||
}
|
||||
ret = 0;
|
||||
break;
|
||||
} while (--retries);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct msrl_action {
|
||||
int err;
|
||||
struct isst_if_mbox_cmd *mbox_cmd;
|
||||
};
|
||||
|
||||
/* revisit, smp_call_function_single should be enough for atomic mailbox! */
|
||||
static void msrl_update_func(void *info)
|
||||
{
|
||||
struct msrl_action *act = info;
|
||||
|
||||
act->err = isst_if_send_mbox_cmd(act->mbox_cmd->command,
|
||||
act->mbox_cmd->sub_command,
|
||||
act->mbox_cmd->parameter,
|
||||
act->mbox_cmd->req_data,
|
||||
&act->mbox_cmd->resp_data);
|
||||
}
|
||||
|
||||
static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume)
|
||||
{
|
||||
struct msrl_action action;
|
||||
int ret;
|
||||
|
||||
action.mbox_cmd = (struct isst_if_mbox_cmd *)cmd_ptr;
|
||||
|
||||
if (isst_if_mbox_cmd_invalid(action.mbox_cmd))
|
||||
return -EINVAL;
|
||||
|
||||
if (isst_if_mbox_cmd_set_req(action.mbox_cmd) &&
|
||||
!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
/*
|
||||
* To complete mailbox command, we need to access two MSRs.
|
||||
* So we don't want race to complete a mailbox transcation.
|
||||
* Here smp_call ensures that msrl_update_func() has no race
|
||||
* and also with wait flag, wait for completion.
|
||||
* smp_call_function_single is using get_cpu() and put_cpu().
|
||||
*/
|
||||
ret = smp_call_function_single(action.mbox_cmd->logical_cpu,
|
||||
msrl_update_func, &action, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!action.err && !resume && isst_if_mbox_cmd_set_req(action.mbox_cmd))
|
||||
action.err = isst_store_cmd(action.mbox_cmd->command,
|
||||
action.mbox_cmd->sub_command,
|
||||
action.mbox_cmd->logical_cpu, 1,
|
||||
action.mbox_cmd->parameter,
|
||||
action.mbox_cmd->req_data);
|
||||
*write_only = 0;
|
||||
|
||||
return action.err;
|
||||
}
|
||||
|
||||
|
||||
static int isst_pm_notify(struct notifier_block *nb,
|
||||
unsigned long mode, void *_unused)
|
||||
{
|
||||
switch (mode) {
|
||||
case PM_POST_HIBERNATION:
|
||||
case PM_POST_RESTORE:
|
||||
case PM_POST_SUSPEND:
|
||||
isst_resume_common();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct notifier_block isst_pm_nb = {
|
||||
.notifier_call = isst_pm_notify,
|
||||
};
|
||||
|
||||
#define ICPU(model) { X86_VENDOR_INTEL, 6, model, X86_FEATURE_ANY, }
|
||||
|
||||
static const struct x86_cpu_id isst_if_cpu_ids[] = {
|
||||
ICPU(INTEL_FAM6_SKYLAKE_X),
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(x86cpu, isst_if_cpu_ids);
|
||||
|
||||
static int __init isst_if_mbox_init(void)
|
||||
{
|
||||
struct isst_if_cmd_cb cb;
|
||||
const struct x86_cpu_id *id;
|
||||
u64 data;
|
||||
int ret;
|
||||
|
||||
id = x86_match_cpu(isst_if_cpu_ids);
|
||||
if (!id)
|
||||
return -ENODEV;
|
||||
|
||||
/* Check presence of mailbox MSRs */
|
||||
ret = rdmsrl_safe(MSR_OS_MAILBOX_INTERFACE, &data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = rdmsrl_safe(MSR_OS_MAILBOX_DATA, &data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
memset(&cb, 0, sizeof(cb));
|
||||
cb.cmd_size = sizeof(struct isst_if_mbox_cmd);
|
||||
cb.offset = offsetof(struct isst_if_mbox_cmds, mbox_cmd);
|
||||
cb.cmd_callback = isst_if_mbox_proc_cmd;
|
||||
cb.owner = THIS_MODULE;
|
||||
ret = isst_if_cdev_register(ISST_IF_DEV_MBOX, &cb);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = register_pm_notifier(&isst_pm_nb);
|
||||
if (ret)
|
||||
isst_if_cdev_unregister(ISST_IF_DEV_MBOX);
|
||||
|
||||
return ret;
|
||||
}
|
||||
module_init(isst_if_mbox_init)
|
||||
|
||||
static void __exit isst_if_mbox_exit(void)
|
||||
{
|
||||
unregister_pm_notifier(&isst_pm_nb);
|
||||
isst_if_cdev_unregister(ISST_IF_DEV_MBOX);
|
||||
}
|
||||
module_exit(isst_if_mbox_exit)
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Intel speed select interface mailbox driver");
|
214
drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c
Arquivo normal
214
drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c
Arquivo normal
@@ -0,0 +1,214 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Intel Speed Select Interface: Mbox via PCI Interface
|
||||
* Copyright (c) 2019, Intel Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/cpufeature.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/sched/signal.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <uapi/linux/isst_if.h>
|
||||
|
||||
#include "isst_if_common.h"
|
||||
|
||||
#define PUNIT_MAILBOX_DATA 0xA0
|
||||
#define PUNIT_MAILBOX_INTERFACE 0xA4
|
||||
#define PUNIT_MAILBOX_BUSY_BIT 31
|
||||
|
||||
/*
|
||||
* Commands has variable amount of processing time. Most of the commands will
|
||||
* be done in 0-3 tries, but some takes up to 50.
|
||||
* The real processing time was observed as 25us for the most of the commands
|
||||
* at 2GHz. It is possible to optimize this count taking samples on customer
|
||||
* systems.
|
||||
*/
|
||||
#define OS_MAILBOX_RETRY_COUNT 50
|
||||
|
||||
struct isst_if_device {
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
||||
static int isst_if_mbox_cmd(struct pci_dev *pdev,
|
||||
struct isst_if_mbox_cmd *mbox_cmd)
|
||||
{
|
||||
u32 retries, data;
|
||||
int ret;
|
||||
|
||||
/* Poll for rb bit == 0 */
|
||||
retries = OS_MAILBOX_RETRY_COUNT;
|
||||
do {
|
||||
ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE,
|
||||
&data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) {
|
||||
ret = -EBUSY;
|
||||
continue;
|
||||
}
|
||||
ret = 0;
|
||||
break;
|
||||
} while (--retries);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Write DATA register */
|
||||
ret = pci_write_config_dword(pdev, PUNIT_MAILBOX_DATA,
|
||||
mbox_cmd->req_data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Write command register */
|
||||
data = BIT_ULL(PUNIT_MAILBOX_BUSY_BIT) |
|
||||
(mbox_cmd->parameter & GENMASK_ULL(13, 0)) << 16 |
|
||||
(mbox_cmd->sub_command << 8) |
|
||||
mbox_cmd->command;
|
||||
|
||||
ret = pci_write_config_dword(pdev, PUNIT_MAILBOX_INTERFACE, data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Poll for rb bit == 0 */
|
||||
retries = OS_MAILBOX_RETRY_COUNT;
|
||||
do {
|
||||
ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE,
|
||||
&data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) {
|
||||
ret = -EBUSY;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data & 0xff)
|
||||
return -ENXIO;
|
||||
|
||||
ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_DATA, &data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mbox_cmd->resp_data = data;
|
||||
ret = 0;
|
||||
break;
|
||||
} while (--retries);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume)
|
||||
{
|
||||
struct isst_if_mbox_cmd *mbox_cmd;
|
||||
struct isst_if_device *punit_dev;
|
||||
struct pci_dev *pdev;
|
||||
int ret;
|
||||
|
||||
mbox_cmd = (struct isst_if_mbox_cmd *)cmd_ptr;
|
||||
|
||||
if (isst_if_mbox_cmd_invalid(mbox_cmd))
|
||||
return -EINVAL;
|
||||
|
||||
if (isst_if_mbox_cmd_set_req(mbox_cmd) && !capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
pdev = isst_if_get_pci_dev(mbox_cmd->logical_cpu, 1, 30, 1);
|
||||
if (!pdev)
|
||||
return -EINVAL;
|
||||
|
||||
punit_dev = pci_get_drvdata(pdev);
|
||||
if (!punit_dev)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Basically we are allowing one complete mailbox transaction on
|
||||
* a mapped PCI device at a time.
|
||||
*/
|
||||
mutex_lock(&punit_dev->mutex);
|
||||
ret = isst_if_mbox_cmd(pdev, mbox_cmd);
|
||||
if (!ret && !resume && isst_if_mbox_cmd_set_req(mbox_cmd))
|
||||
ret = isst_store_cmd(mbox_cmd->command,
|
||||
mbox_cmd->sub_command,
|
||||
mbox_cmd->logical_cpu, 1,
|
||||
mbox_cmd->parameter,
|
||||
mbox_cmd->req_data);
|
||||
mutex_unlock(&punit_dev->mutex);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*write_only = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pci_device_id isst_if_mbox_ids[] = {
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_CFG_MBOX_DEVID_0)},
|
||||
{ 0 },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, isst_if_mbox_ids);
|
||||
|
||||
static int isst_if_mbox_probe(struct pci_dev *pdev,
|
||||
const struct pci_device_id *ent)
|
||||
{
|
||||
struct isst_if_device *punit_dev;
|
||||
struct isst_if_cmd_cb cb;
|
||||
int ret;
|
||||
|
||||
punit_dev = devm_kzalloc(&pdev->dev, sizeof(*punit_dev), GFP_KERNEL);
|
||||
if (!punit_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = pcim_enable_device(pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_init(&punit_dev->mutex);
|
||||
pci_set_drvdata(pdev, punit_dev);
|
||||
|
||||
memset(&cb, 0, sizeof(cb));
|
||||
cb.cmd_size = sizeof(struct isst_if_mbox_cmd);
|
||||
cb.offset = offsetof(struct isst_if_mbox_cmds, mbox_cmd);
|
||||
cb.cmd_callback = isst_if_mbox_proc_cmd;
|
||||
cb.owner = THIS_MODULE;
|
||||
ret = isst_if_cdev_register(ISST_IF_DEV_MBOX, &cb);
|
||||
|
||||
if (ret)
|
||||
mutex_destroy(&punit_dev->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void isst_if_mbox_remove(struct pci_dev *pdev)
|
||||
{
|
||||
struct isst_if_device *punit_dev;
|
||||
|
||||
punit_dev = pci_get_drvdata(pdev);
|
||||
isst_if_cdev_unregister(ISST_IF_DEV_MBOX);
|
||||
mutex_destroy(&punit_dev->mutex);
|
||||
}
|
||||
|
||||
static int __maybe_unused isst_if_resume(struct device *device)
|
||||
{
|
||||
isst_resume_common();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(isst_if_pm_ops, NULL, isst_if_resume);
|
||||
|
||||
static struct pci_driver isst_if_pci_driver = {
|
||||
.name = "isst_if_mbox_pci",
|
||||
.id_table = isst_if_mbox_ids,
|
||||
.probe = isst_if_mbox_probe,
|
||||
.remove = isst_if_mbox_remove,
|
||||
.driver.pm = &isst_if_pm_ops,
|
||||
};
|
||||
|
||||
module_pci_driver(isst_if_pci_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Intel speed select interface pci mailbox driver");
|
180
drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c
Arquivo normal
180
drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c
Arquivo normal
@@ -0,0 +1,180 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Intel Speed Select Interface: MMIO Interface
|
||||
* Copyright (c) 2019, Intel Corporation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/sched/signal.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <uapi/linux/isst_if.h>
|
||||
|
||||
#include "isst_if_common.h"
|
||||
|
||||
struct isst_mmio_range {
|
||||
int beg;
|
||||
int end;
|
||||
};
|
||||
|
||||
struct isst_mmio_range mmio_range[] = {
|
||||
{0x04, 0x14},
|
||||
{0x20, 0xD0},
|
||||
};
|
||||
|
||||
struct isst_if_device {
|
||||
void __iomem *punit_mmio;
|
||||
u32 range_0[5];
|
||||
u32 range_1[45];
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
||||
static long isst_if_mmio_rd_wr(u8 *cmd_ptr, int *write_only, int resume)
|
||||
{
|
||||
struct isst_if_device *punit_dev;
|
||||
struct isst_if_io_reg *io_reg;
|
||||
struct pci_dev *pdev;
|
||||
|
||||
io_reg = (struct isst_if_io_reg *)cmd_ptr;
|
||||
if (io_reg->reg < 0x04 || io_reg->reg > 0xD0)
|
||||
return -EINVAL;
|
||||
|
||||
if (io_reg->read_write && !capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
pdev = isst_if_get_pci_dev(io_reg->logical_cpu, 0, 0, 1);
|
||||
if (!pdev)
|
||||
return -EINVAL;
|
||||
|
||||
punit_dev = pci_get_drvdata(pdev);
|
||||
if (!punit_dev)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Ensure that operation is complete on a PCI device to avoid read
|
||||
* write race by using per PCI device mutex.
|
||||
*/
|
||||
mutex_lock(&punit_dev->mutex);
|
||||
if (io_reg->read_write) {
|
||||
writel(io_reg->value, punit_dev->punit_mmio+io_reg->reg);
|
||||
*write_only = 1;
|
||||
} else {
|
||||
io_reg->value = readl(punit_dev->punit_mmio+io_reg->reg);
|
||||
*write_only = 0;
|
||||
}
|
||||
mutex_unlock(&punit_dev->mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pci_device_id isst_if_ids[] = {
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_RAPL_PRIO_DEVID_0)},
|
||||
{ 0 },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, isst_if_ids);
|
||||
|
||||
static int isst_if_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||
{
|
||||
struct isst_if_device *punit_dev;
|
||||
struct isst_if_cmd_cb cb;
|
||||
u32 mmio_base, pcu_base;
|
||||
u64 base_addr;
|
||||
int ret;
|
||||
|
||||
punit_dev = devm_kzalloc(&pdev->dev, sizeof(*punit_dev), GFP_KERNEL);
|
||||
if (!punit_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = pcim_enable_device(pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = pci_read_config_dword(pdev, 0xD0, &mmio_base);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = pci_read_config_dword(pdev, 0xFC, &pcu_base);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pcu_base &= GENMASK(10, 0);
|
||||
base_addr = (u64)mmio_base << 23 | (u64) pcu_base << 12;
|
||||
punit_dev->punit_mmio = devm_ioremap(&pdev->dev, base_addr, 256);
|
||||
if (!punit_dev->punit_mmio)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&punit_dev->mutex);
|
||||
pci_set_drvdata(pdev, punit_dev);
|
||||
|
||||
memset(&cb, 0, sizeof(cb));
|
||||
cb.cmd_size = sizeof(struct isst_if_io_reg);
|
||||
cb.offset = offsetof(struct isst_if_io_regs, io_reg);
|
||||
cb.cmd_callback = isst_if_mmio_rd_wr;
|
||||
cb.owner = THIS_MODULE;
|
||||
ret = isst_if_cdev_register(ISST_IF_DEV_MMIO, &cb);
|
||||
if (ret)
|
||||
mutex_destroy(&punit_dev->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void isst_if_remove(struct pci_dev *pdev)
|
||||
{
|
||||
struct isst_if_device *punit_dev;
|
||||
|
||||
punit_dev = pci_get_drvdata(pdev);
|
||||
isst_if_cdev_unregister(ISST_IF_DEV_MBOX);
|
||||
mutex_destroy(&punit_dev->mutex);
|
||||
}
|
||||
|
||||
static int __maybe_unused isst_if_suspend(struct device *device)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(device);
|
||||
struct isst_if_device *punit_dev;
|
||||
int i;
|
||||
|
||||
punit_dev = pci_get_drvdata(pdev);
|
||||
for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i)
|
||||
punit_dev->range_0[i] = readl(punit_dev->punit_mmio +
|
||||
mmio_range[0].beg + 4 * i);
|
||||
for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i)
|
||||
punit_dev->range_1[i] = readl(punit_dev->punit_mmio +
|
||||
mmio_range[1].beg + 4 * i);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused isst_if_resume(struct device *device)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(device);
|
||||
struct isst_if_device *punit_dev;
|
||||
int i;
|
||||
|
||||
punit_dev = pci_get_drvdata(pdev);
|
||||
for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i)
|
||||
writel(punit_dev->range_0[i], punit_dev->punit_mmio +
|
||||
mmio_range[0].beg + 4 * i);
|
||||
for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i)
|
||||
writel(punit_dev->range_1[i], punit_dev->punit_mmio +
|
||||
mmio_range[1].beg + 4 * i);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(isst_if_pm_ops, isst_if_suspend, isst_if_resume);
|
||||
|
||||
static struct pci_driver isst_if_pci_driver = {
|
||||
.name = "isst_if_pci",
|
||||
.id_table = isst_if_ids,
|
||||
.probe = isst_if_probe,
|
||||
.remove = isst_if_remove,
|
||||
.driver.pm = &isst_if_pm_ops,
|
||||
};
|
||||
|
||||
module_pci_driver(isst_if_pci_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Intel speed select interface mmio driver");
|
@@ -900,7 +900,7 @@ static int __init telemetry_debugfs_init(void)
|
||||
{
|
||||
const struct x86_cpu_id *id;
|
||||
int err;
|
||||
struct dentry *f;
|
||||
struct dentry *dir;
|
||||
|
||||
/* Only APL supported for now */
|
||||
id = x86_match_cpu(telemetry_debugfs_cpu_ids);
|
||||
@@ -923,68 +923,22 @@ static int __init telemetry_debugfs_init(void)
|
||||
|
||||
register_pm_notifier(&pm_notifier);
|
||||
|
||||
err = -ENOMEM;
|
||||
debugfs_conf->telemetry_dbg_dir = debugfs_create_dir("telemetry", NULL);
|
||||
if (!debugfs_conf->telemetry_dbg_dir)
|
||||
goto out_pm;
|
||||
|
||||
f = debugfs_create_file("pss_info", S_IFREG | S_IRUGO,
|
||||
debugfs_conf->telemetry_dbg_dir, NULL,
|
||||
&telem_pss_states_fops);
|
||||
if (!f) {
|
||||
pr_err("pss_sample_info debugfs register failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
f = debugfs_create_file("ioss_info", S_IFREG | S_IRUGO,
|
||||
debugfs_conf->telemetry_dbg_dir, NULL,
|
||||
&telem_ioss_states_fops);
|
||||
if (!f) {
|
||||
pr_err("ioss_sample_info debugfs register failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
f = debugfs_create_file("soc_states", S_IFREG | S_IRUGO,
|
||||
debugfs_conf->telemetry_dbg_dir,
|
||||
NULL, &telem_soc_states_fops);
|
||||
if (!f) {
|
||||
pr_err("ioss_sample_info debugfs register failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
f = debugfs_create_file("s0ix_residency_usec", S_IFREG | S_IRUGO,
|
||||
debugfs_conf->telemetry_dbg_dir,
|
||||
NULL, &telem_s0ix_fops);
|
||||
if (!f) {
|
||||
pr_err("s0ix_residency_usec debugfs register failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
f = debugfs_create_file("pss_trace_verbosity", S_IFREG | S_IRUGO,
|
||||
debugfs_conf->telemetry_dbg_dir, NULL,
|
||||
&telem_pss_trc_verb_ops);
|
||||
if (!f) {
|
||||
pr_err("pss_trace_verbosity debugfs register failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
f = debugfs_create_file("ioss_trace_verbosity", S_IFREG | S_IRUGO,
|
||||
debugfs_conf->telemetry_dbg_dir, NULL,
|
||||
&telem_ioss_trc_verb_ops);
|
||||
if (!f) {
|
||||
pr_err("ioss_trace_verbosity debugfs register failed\n");
|
||||
goto out;
|
||||
}
|
||||
dir = debugfs_create_dir("telemetry", NULL);
|
||||
debugfs_conf->telemetry_dbg_dir = dir;
|
||||
|
||||
debugfs_create_file("pss_info", S_IFREG | S_IRUGO, dir, NULL,
|
||||
&telem_pss_states_fops);
|
||||
debugfs_create_file("ioss_info", S_IFREG | S_IRUGO, dir, NULL,
|
||||
&telem_ioss_states_fops);
|
||||
debugfs_create_file("soc_states", S_IFREG | S_IRUGO, dir, NULL,
|
||||
&telem_soc_states_fops);
|
||||
debugfs_create_file("s0ix_residency_usec", S_IFREG | S_IRUGO, dir, NULL,
|
||||
&telem_s0ix_fops);
|
||||
debugfs_create_file("pss_trace_verbosity", S_IFREG | S_IRUGO, dir, NULL,
|
||||
&telem_pss_trc_verb_ops);
|
||||
debugfs_create_file("ioss_trace_verbosity", S_IFREG | S_IRUGO, dir,
|
||||
NULL, &telem_ioss_trc_verb_ops);
|
||||
return 0;
|
||||
|
||||
out:
|
||||
debugfs_remove_recursive(debugfs_conf->telemetry_dbg_dir);
|
||||
debugfs_conf->telemetry_dbg_dir = NULL;
|
||||
out_pm:
|
||||
unregister_pm_notifier(&pm_notifier);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit telemetry_debugfs_exit(void)
|
||||
|
@@ -44,6 +44,8 @@
|
||||
#define MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET 0x3b
|
||||
#define MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET 0x40
|
||||
#define MLXPLAT_CPLD_LPC_REG_AGGRLO_MASK_OFFSET 0x41
|
||||
#define MLXPLAT_CPLD_LPC_REG_AGGRCO_OFFSET 0x42
|
||||
#define MLXPLAT_CPLD_LPC_REG_AGGRCO_MASK_OFFSET 0x43
|
||||
#define MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET 0x50
|
||||
#define MLXPLAT_CPLD_LPC_REG_ASIC_EVENT_OFFSET 0x51
|
||||
#define MLXPLAT_CPLD_LPC_REG_ASIC_MASK_OFFSET 0x52
|
||||
@@ -105,7 +107,9 @@
|
||||
MLXPLAT_CPLD_AGGR_FAN_MASK_DEF)
|
||||
#define MLXPLAT_CPLD_AGGR_ASIC_MASK_NG 0x01
|
||||
#define MLXPLAT_CPLD_AGGR_MASK_NG_DEF 0x04
|
||||
#define MLXPLAT_CPLD_AGGR_MASK_COMEX BIT(0)
|
||||
#define MLXPLAT_CPLD_LOW_AGGR_MASK_LOW 0xc1
|
||||
#define MLXPLAT_CPLD_LOW_AGGR_MASK_I2C BIT(6)
|
||||
#define MLXPLAT_CPLD_PSU_MASK GENMASK(1, 0)
|
||||
#define MLXPLAT_CPLD_PWR_MASK GENMASK(1, 0)
|
||||
#define MLXPLAT_CPLD_FAN_MASK GENMASK(3, 0)
|
||||
@@ -159,6 +163,7 @@
|
||||
* @pdev_io_regs - register access platform devices
|
||||
* @pdev_fan - FAN platform devices
|
||||
* @pdev_wd - array of watchdog platform devices
|
||||
* @regmap: device register map
|
||||
*/
|
||||
struct mlxplat_priv {
|
||||
struct platform_device *pdev_i2c;
|
||||
@@ -168,6 +173,7 @@ struct mlxplat_priv {
|
||||
struct platform_device *pdev_io_regs;
|
||||
struct platform_device *pdev_fan;
|
||||
struct platform_device *pdev_wd[MLXPLAT_CPLD_WD_MAX_DEVS];
|
||||
void *regmap;
|
||||
};
|
||||
|
||||
/* Regions for LPC I2C controller and LPC base register space */
|
||||
@@ -181,6 +187,14 @@ static const struct resource mlxplat_lpc_resources[] = {
|
||||
IORESOURCE_IO),
|
||||
};
|
||||
|
||||
/* Platform next generation systems i2c data */
|
||||
static struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_i2c_ng_data = {
|
||||
.cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET,
|
||||
.mask = MLXPLAT_CPLD_AGGR_MASK_COMEX,
|
||||
.cell_low = MLXPLAT_CPLD_LPC_REG_AGGRCO_OFFSET,
|
||||
.mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_I2C,
|
||||
};
|
||||
|
||||
/* Platform default channels */
|
||||
static const int mlxplat_default_channels[][MLXPLAT_CPLD_GRP_CHNL_NUM] = {
|
||||
{
|
||||
@@ -704,7 +718,7 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_default_ng_data = {
|
||||
.items = mlxplat_mlxcpld_default_ng_items,
|
||||
.counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_items),
|
||||
.cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET,
|
||||
.mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF,
|
||||
.mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX,
|
||||
.cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET,
|
||||
.mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW,
|
||||
};
|
||||
@@ -1112,6 +1126,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_msn21xx_regs_io_data[] = {
|
||||
.mask = GENMASK(7, 0) & ~BIT(6),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_sff_wd",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(6),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "psu1_on",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
|
||||
@@ -1200,6 +1220,18 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = {
|
||||
.mask = GENMASK(7, 0) & ~BIT(4),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_from_asic",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(5),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_swb_wd",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(6),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_asic_thermal",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
|
||||
@@ -1212,6 +1244,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = {
|
||||
.mask = GENMASK(7, 0) & ~BIT(3),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_comex_wd",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(6),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_voltmon_upgrade_fail",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
|
||||
@@ -1224,6 +1262,18 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = {
|
||||
.mask = GENMASK(7, 0) & ~BIT(1),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_comex_thermal",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(3),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "reset_reload_bios",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
|
||||
.mask = GENMASK(7, 0) & ~BIT(5),
|
||||
.mode = 0444,
|
||||
},
|
||||
{
|
||||
.label = "psu1_on",
|
||||
.reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
|
||||
@@ -1531,6 +1581,7 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg)
|
||||
case MLXPLAT_CPLD_LPC_REG_WP2_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_AGGRLO_MASK_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_AGGRCO_MASK_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_ASIC_EVENT_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_ASIC_MASK_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFFSET:
|
||||
@@ -1578,6 +1629,8 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg)
|
||||
case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_AGGRLO_MASK_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_AGGRCO_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_AGGRCO_MASK_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_ASIC_EVENT_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_ASIC_MASK_OFFSET:
|
||||
@@ -1645,6 +1698,8 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg)
|
||||
case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_AGGRLO_MASK_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_AGGRCO_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_AGGRCO_MASK_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_ASIC_EVENT_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_ASIC_MASK_OFFSET:
|
||||
@@ -1691,6 +1746,11 @@ static const struct reg_default mlxplat_mlxcpld_regmap_default[] = {
|
||||
{ MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET, 0x00 },
|
||||
};
|
||||
|
||||
static const struct reg_default mlxplat_mlxcpld_regmap_ng[] = {
|
||||
{ MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, 0x00 },
|
||||
{ MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET, 0x00 },
|
||||
};
|
||||
|
||||
struct mlxplat_mlxcpld_regmap_context {
|
||||
void __iomem *base;
|
||||
};
|
||||
@@ -1729,17 +1789,33 @@ static const struct regmap_config mlxplat_mlxcpld_regmap_config = {
|
||||
.reg_write = mlxplat_mlxcpld_reg_write,
|
||||
};
|
||||
|
||||
static const struct regmap_config mlxplat_mlxcpld_regmap_config_ng = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = 255,
|
||||
.cache_type = REGCACHE_FLAT,
|
||||
.writeable_reg = mlxplat_mlxcpld_writeable_reg,
|
||||
.readable_reg = mlxplat_mlxcpld_readable_reg,
|
||||
.volatile_reg = mlxplat_mlxcpld_volatile_reg,
|
||||
.reg_defaults = mlxplat_mlxcpld_regmap_ng,
|
||||
.num_reg_defaults = ARRAY_SIZE(mlxplat_mlxcpld_regmap_ng),
|
||||
.reg_read = mlxplat_mlxcpld_reg_read,
|
||||
.reg_write = mlxplat_mlxcpld_reg_write,
|
||||
};
|
||||
|
||||
static struct resource mlxplat_mlxcpld_resources[] = {
|
||||
[0] = DEFINE_RES_IRQ_NAMED(17, "mlxreg-hotplug"),
|
||||
};
|
||||
|
||||
static struct platform_device *mlxplat_dev;
|
||||
static struct mlxreg_core_hotplug_platform_data *mlxplat_i2c;
|
||||
static struct mlxreg_core_hotplug_platform_data *mlxplat_hotplug;
|
||||
static struct mlxreg_core_platform_data *mlxplat_led;
|
||||
static struct mlxreg_core_platform_data *mlxplat_regs_io;
|
||||
static struct mlxreg_core_platform_data *mlxplat_fan;
|
||||
static struct mlxreg_core_platform_data
|
||||
*mlxplat_wd_data[MLXPLAT_CPLD_WD_MAX_DEVS];
|
||||
static const struct regmap_config *mlxplat_regmap_config;
|
||||
|
||||
static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi)
|
||||
{
|
||||
@@ -1834,11 +1910,49 @@ static int __init mlxplat_dmi_qmb7xx_matched(const struct dmi_system_id *dmi)
|
||||
mlxplat_fan = &mlxplat_default_fan_data;
|
||||
for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++)
|
||||
mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i];
|
||||
mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
|
||||
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng;
|
||||
|
||||
return 1;
|
||||
};
|
||||
|
||||
static const struct dmi_system_id mlxplat_dmi_table[] __initconst = {
|
||||
{
|
||||
.callback = mlxplat_dmi_default_matched,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "VMOD0001"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.callback = mlxplat_dmi_msn21xx_matched,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "VMOD0002"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.callback = mlxplat_dmi_msn274x_matched,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "VMOD0003"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.callback = mlxplat_dmi_msn201x_matched,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "VMOD0004"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.callback = mlxplat_dmi_qmb7xx_matched,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "VMOD0005"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.callback = mlxplat_dmi_qmb7xx_matched,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "VMOD0007"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.callback = mlxplat_dmi_msn274x_matched,
|
||||
.matches = {
|
||||
@@ -1916,42 +2030,6 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = {
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "MSN38"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.callback = mlxplat_dmi_default_matched,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "VMOD0001"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.callback = mlxplat_dmi_msn21xx_matched,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "VMOD0002"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.callback = mlxplat_dmi_msn274x_matched,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "VMOD0003"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.callback = mlxplat_dmi_msn201x_matched,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "VMOD0004"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.callback = mlxplat_dmi_qmb7xx_matched,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "VMOD0005"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.callback = mlxplat_dmi_qmb7xx_matched,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "VMOD0007"),
|
||||
},
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
@@ -2018,13 +2096,36 @@ static int __init mlxplat_init(void)
|
||||
}
|
||||
platform_set_drvdata(mlxplat_dev, priv);
|
||||
|
||||
mlxplat_mlxcpld_regmap_ctx.base = devm_ioport_map(&mlxplat_dev->dev,
|
||||
mlxplat_lpc_resources[1].start, 1);
|
||||
if (!mlxplat_mlxcpld_regmap_ctx.base) {
|
||||
err = -ENOMEM;
|
||||
goto fail_alloc;
|
||||
}
|
||||
|
||||
if (!mlxplat_regmap_config)
|
||||
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config;
|
||||
|
||||
priv->regmap = devm_regmap_init(&mlxplat_dev->dev, NULL,
|
||||
&mlxplat_mlxcpld_regmap_ctx,
|
||||
mlxplat_regmap_config);
|
||||
if (IS_ERR(priv->regmap)) {
|
||||
err = PTR_ERR(priv->regmap);
|
||||
goto fail_alloc;
|
||||
}
|
||||
|
||||
err = mlxplat_mlxcpld_verify_bus_topology(&nr);
|
||||
if (nr < 0)
|
||||
goto fail_alloc;
|
||||
|
||||
nr = (nr == MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM) ? -1 : nr;
|
||||
priv->pdev_i2c = platform_device_register_simple("i2c_mlxcpld", nr,
|
||||
NULL, 0);
|
||||
if (mlxplat_i2c)
|
||||
mlxplat_i2c->regmap = priv->regmap;
|
||||
priv->pdev_i2c = platform_device_register_resndata(
|
||||
&mlxplat_dev->dev, "i2c_mlxcpld",
|
||||
nr, mlxplat_mlxcpld_resources,
|
||||
ARRAY_SIZE(mlxplat_mlxcpld_resources),
|
||||
mlxplat_i2c, sizeof(*mlxplat_i2c));
|
||||
if (IS_ERR(priv->pdev_i2c)) {
|
||||
err = PTR_ERR(priv->pdev_i2c);
|
||||
goto fail_alloc;
|
||||
@@ -2042,21 +2143,8 @@ static int __init mlxplat_init(void)
|
||||
}
|
||||
}
|
||||
|
||||
mlxplat_mlxcpld_regmap_ctx.base = devm_ioport_map(&mlxplat_dev->dev,
|
||||
mlxplat_lpc_resources[1].start, 1);
|
||||
if (!mlxplat_mlxcpld_regmap_ctx.base) {
|
||||
err = -ENOMEM;
|
||||
goto fail_platform_mux_register;
|
||||
}
|
||||
|
||||
mlxplat_hotplug->regmap = devm_regmap_init(&mlxplat_dev->dev, NULL,
|
||||
&mlxplat_mlxcpld_regmap_ctx,
|
||||
&mlxplat_mlxcpld_regmap_config);
|
||||
if (IS_ERR(mlxplat_hotplug->regmap)) {
|
||||
err = PTR_ERR(mlxplat_hotplug->regmap);
|
||||
goto fail_platform_mux_register;
|
||||
}
|
||||
|
||||
/* Add hotplug driver */
|
||||
mlxplat_hotplug->regmap = priv->regmap;
|
||||
priv->pdev_hotplug = platform_device_register_resndata(
|
||||
&mlxplat_dev->dev, "mlxreg-hotplug",
|
||||
PLATFORM_DEVID_NONE,
|
||||
@@ -2069,16 +2157,16 @@ static int __init mlxplat_init(void)
|
||||
}
|
||||
|
||||
/* Set default registers. */
|
||||
for (j = 0; j < mlxplat_mlxcpld_regmap_config.num_reg_defaults; j++) {
|
||||
err = regmap_write(mlxplat_hotplug->regmap,
|
||||
mlxplat_mlxcpld_regmap_default[j].reg,
|
||||
mlxplat_mlxcpld_regmap_default[j].def);
|
||||
for (j = 0; j < mlxplat_regmap_config->num_reg_defaults; j++) {
|
||||
err = regmap_write(priv->regmap,
|
||||
mlxplat_regmap_config->reg_defaults[j].reg,
|
||||
mlxplat_regmap_config->reg_defaults[j].def);
|
||||
if (err)
|
||||
goto fail_platform_mux_register;
|
||||
}
|
||||
|
||||
/* Add LED driver. */
|
||||
mlxplat_led->regmap = mlxplat_hotplug->regmap;
|
||||
mlxplat_led->regmap = priv->regmap;
|
||||
priv->pdev_led = platform_device_register_resndata(
|
||||
&mlxplat_dev->dev, "leds-mlxreg",
|
||||
PLATFORM_DEVID_NONE, NULL, 0,
|
||||
@@ -2090,7 +2178,7 @@ static int __init mlxplat_init(void)
|
||||
|
||||
/* Add registers io access driver. */
|
||||
if (mlxplat_regs_io) {
|
||||
mlxplat_regs_io->regmap = mlxplat_hotplug->regmap;
|
||||
mlxplat_regs_io->regmap = priv->regmap;
|
||||
priv->pdev_io_regs = platform_device_register_resndata(
|
||||
&mlxplat_dev->dev, "mlxreg-io",
|
||||
PLATFORM_DEVID_NONE, NULL, 0,
|
||||
@@ -2104,7 +2192,7 @@ static int __init mlxplat_init(void)
|
||||
|
||||
/* Add FAN driver. */
|
||||
if (mlxplat_fan) {
|
||||
mlxplat_fan->regmap = mlxplat_hotplug->regmap;
|
||||
mlxplat_fan->regmap = priv->regmap;
|
||||
priv->pdev_fan = platform_device_register_resndata(
|
||||
&mlxplat_dev->dev, "mlxreg-fan",
|
||||
PLATFORM_DEVID_NONE, NULL, 0,
|
||||
@@ -2119,7 +2207,7 @@ static int __init mlxplat_init(void)
|
||||
/* Add WD drivers. */
|
||||
for (j = 0; j < MLXPLAT_CPLD_WD_MAX_DEVS; j++) {
|
||||
if (mlxplat_wd_data[j]) {
|
||||
mlxplat_wd_data[j]->regmap = mlxplat_hotplug->regmap;
|
||||
mlxplat_wd_data[j]->regmap = priv->regmap;
|
||||
priv->pdev_wd[j] = platform_device_register_resndata(
|
||||
&mlxplat_dev->dev, "mlx-wdt",
|
||||
j, NULL, 0,
|
||||
@@ -2133,8 +2221,8 @@ static int __init mlxplat_init(void)
|
||||
}
|
||||
|
||||
/* Sync registers with hardware. */
|
||||
regcache_mark_dirty(mlxplat_hotplug->regmap);
|
||||
err = regcache_sync(mlxplat_hotplug->regmap);
|
||||
regcache_mark_dirty(priv->regmap);
|
||||
err = regcache_sync(priv->regmap);
|
||||
if (err)
|
||||
goto fail_platform_wd_register;
|
||||
|
||||
|
@@ -77,7 +77,7 @@ static const struct gpio_led_platform_data apu2_leds_pdata = {
|
||||
.leds = apu2_leds,
|
||||
};
|
||||
|
||||
struct gpiod_lookup_table gpios_led_table = {
|
||||
static struct gpiod_lookup_table gpios_led_table = {
|
||||
.dev_id = "leds-gpio",
|
||||
.table = {
|
||||
GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_LED1,
|
||||
@@ -93,7 +93,7 @@ struct gpiod_lookup_table gpios_led_table = {
|
||||
|
||||
static struct gpio_keys_button apu2_keys_buttons[] = {
|
||||
{
|
||||
.code = KEY_SETUP,
|
||||
.code = KEY_RESTART,
|
||||
.active_low = 1,
|
||||
.desc = "front button",
|
||||
.type = EV_KEY,
|
||||
@@ -110,7 +110,7 @@ static const struct gpio_keys_platform_data apu2_keys_pdata = {
|
||||
.name = "apu2-keys",
|
||||
};
|
||||
|
||||
struct gpiod_lookup_table gpios_key_table = {
|
||||
static struct gpiod_lookup_table gpios_key_table = {
|
||||
.dev_id = "gpio-keys-polled",
|
||||
.table = {
|
||||
GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_MODESW,
|
||||
@@ -255,6 +255,4 @@ MODULE_DESCRIPTION("PC Engines APUv2/APUv3 board GPIO/LED/keys driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DEVICE_TABLE(dmi, apu_gpio_dmi_table);
|
||||
MODULE_ALIAS("platform:pcengines-apuv2");
|
||||
MODULE_SOFTDEP("pre: platform:" AMD_FCH_GPIO_DRIVER_NAME);
|
||||
MODULE_SOFTDEP("pre: platform:leds-gpio");
|
||||
MODULE_SOFTDEP("pre: platform:gpio_keys_polled");
|
||||
MODULE_SOFTDEP("pre: platform:" AMD_FCH_GPIO_DRIVER_NAME " platform:leds-gpio platform:gpio_keys_polled");
|
||||
|
@@ -341,45 +341,24 @@ static int pmc_sleep_tmr_show(struct seq_file *s, void *unused)
|
||||
|
||||
DEFINE_SHOW_ATTRIBUTE(pmc_sleep_tmr);
|
||||
|
||||
static void pmc_dbgfs_unregister(struct pmc_dev *pmc)
|
||||
static void pmc_dbgfs_register(struct pmc_dev *pmc)
|
||||
{
|
||||
debugfs_remove_recursive(pmc->dbgfs_dir);
|
||||
}
|
||||
|
||||
static int pmc_dbgfs_register(struct pmc_dev *pmc)
|
||||
{
|
||||
struct dentry *dir, *f;
|
||||
struct dentry *dir;
|
||||
|
||||
dir = debugfs_create_dir("pmc_atom", NULL);
|
||||
if (!dir)
|
||||
return -ENOMEM;
|
||||
|
||||
pmc->dbgfs_dir = dir;
|
||||
|
||||
f = debugfs_create_file("dev_state", S_IFREG | S_IRUGO,
|
||||
dir, pmc, &pmc_dev_state_fops);
|
||||
if (!f)
|
||||
goto err;
|
||||
|
||||
f = debugfs_create_file("pss_state", S_IFREG | S_IRUGO,
|
||||
dir, pmc, &pmc_pss_state_fops);
|
||||
if (!f)
|
||||
goto err;
|
||||
|
||||
f = debugfs_create_file("sleep_state", S_IFREG | S_IRUGO,
|
||||
dir, pmc, &pmc_sleep_tmr_fops);
|
||||
if (!f)
|
||||
goto err;
|
||||
|
||||
return 0;
|
||||
err:
|
||||
pmc_dbgfs_unregister(pmc);
|
||||
return -ENODEV;
|
||||
debugfs_create_file("dev_state", S_IFREG | S_IRUGO, dir, pmc,
|
||||
&pmc_dev_state_fops);
|
||||
debugfs_create_file("pss_state", S_IFREG | S_IRUGO, dir, pmc,
|
||||
&pmc_pss_state_fops);
|
||||
debugfs_create_file("sleep_state", S_IFREG | S_IRUGO, dir, pmc,
|
||||
&pmc_sleep_tmr_fops);
|
||||
}
|
||||
#else
|
||||
static int pmc_dbgfs_register(struct pmc_dev *pmc)
|
||||
static void pmc_dbgfs_register(struct pmc_dev *pmc)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_DEBUG_FS */
|
||||
|
||||
@@ -412,6 +391,14 @@ static const struct dmi_system_id critclk_systems[] = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "CB3163"),
|
||||
},
|
||||
},
|
||||
{
|
||||
/* pmc_plt_clk* - are used for ethernet controllers */
|
||||
.ident = "Beckhoff CB4063",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Beckhoff Automation"),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "CB4063"),
|
||||
},
|
||||
},
|
||||
{
|
||||
/* pmc_plt_clk* - are used for ethernet controllers */
|
||||
.ident = "Beckhoff CB6263",
|
||||
@@ -491,9 +478,7 @@ static int pmc_setup_dev(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||
/* PMC hardware registers setup */
|
||||
pmc_hw_reg_setup(pmc);
|
||||
|
||||
ret = pmc_dbgfs_register(pmc);
|
||||
if (ret)
|
||||
dev_warn(&pdev->dev, "debugfs register failed\n");
|
||||
pmc_dbgfs_register(pmc);
|
||||
|
||||
/* Register platform clocks - PMC_PLT_CLK [0..5] */
|
||||
ret = pmc_setup_clks(pdev, pmc->regmap, data);
|
||||
|
@@ -1276,15 +1276,12 @@ static void samsung_debugfs_exit(struct samsung_laptop *samsung)
|
||||
debugfs_remove_recursive(samsung->debug.root);
|
||||
}
|
||||
|
||||
static int samsung_debugfs_init(struct samsung_laptop *samsung)
|
||||
static void samsung_debugfs_init(struct samsung_laptop *samsung)
|
||||
{
|
||||
struct dentry *dent;
|
||||
struct dentry *root;
|
||||
|
||||
samsung->debug.root = debugfs_create_dir("samsung-laptop", NULL);
|
||||
if (!samsung->debug.root) {
|
||||
pr_err("failed to create debugfs directory");
|
||||
goto error_debugfs;
|
||||
}
|
||||
root = debugfs_create_dir("samsung-laptop", NULL);
|
||||
samsung->debug.root = root;
|
||||
|
||||
samsung->debug.f0000_wrapper.data = samsung->f0000_segment;
|
||||
samsung->debug.f0000_wrapper.size = 0xffff;
|
||||
@@ -1295,60 +1292,24 @@ static int samsung_debugfs_init(struct samsung_laptop *samsung)
|
||||
samsung->debug.sdiag_wrapper.data = samsung->sdiag;
|
||||
samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag);
|
||||
|
||||
dent = debugfs_create_u16("command", S_IRUGO | S_IWUSR,
|
||||
samsung->debug.root, &samsung->debug.command);
|
||||
if (!dent)
|
||||
goto error_debugfs;
|
||||
|
||||
dent = debugfs_create_u32("d0", S_IRUGO | S_IWUSR, samsung->debug.root,
|
||||
&samsung->debug.data.d0);
|
||||
if (!dent)
|
||||
goto error_debugfs;
|
||||
|
||||
dent = debugfs_create_u32("d1", S_IRUGO | S_IWUSR, samsung->debug.root,
|
||||
&samsung->debug.data.d1);
|
||||
if (!dent)
|
||||
goto error_debugfs;
|
||||
|
||||
dent = debugfs_create_u16("d2", S_IRUGO | S_IWUSR, samsung->debug.root,
|
||||
&samsung->debug.data.d2);
|
||||
if (!dent)
|
||||
goto error_debugfs;
|
||||
|
||||
dent = debugfs_create_u8("d3", S_IRUGO | S_IWUSR, samsung->debug.root,
|
||||
&samsung->debug.data.d3);
|
||||
if (!dent)
|
||||
goto error_debugfs;
|
||||
|
||||
dent = debugfs_create_blob("data", S_IRUGO | S_IWUSR,
|
||||
samsung->debug.root,
|
||||
&samsung->debug.data_wrapper);
|
||||
if (!dent)
|
||||
goto error_debugfs;
|
||||
|
||||
dent = debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR,
|
||||
samsung->debug.root,
|
||||
&samsung->debug.f0000_wrapper);
|
||||
if (!dent)
|
||||
goto error_debugfs;
|
||||
|
||||
dent = debugfs_create_file("call", S_IFREG | S_IRUGO,
|
||||
samsung->debug.root, samsung,
|
||||
&samsung_laptop_call_fops);
|
||||
if (!dent)
|
||||
goto error_debugfs;
|
||||
|
||||
dent = debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR,
|
||||
samsung->debug.root,
|
||||
&samsung->debug.sdiag_wrapper);
|
||||
if (!dent)
|
||||
goto error_debugfs;
|
||||
|
||||
return 0;
|
||||
|
||||
error_debugfs:
|
||||
samsung_debugfs_exit(samsung);
|
||||
return -ENOMEM;
|
||||
debugfs_create_u16("command", S_IRUGO | S_IWUSR, root,
|
||||
&samsung->debug.command);
|
||||
debugfs_create_u32("d0", S_IRUGO | S_IWUSR, root,
|
||||
&samsung->debug.data.d0);
|
||||
debugfs_create_u32("d1", S_IRUGO | S_IWUSR, root,
|
||||
&samsung->debug.data.d1);
|
||||
debugfs_create_u16("d2", S_IRUGO | S_IWUSR, root,
|
||||
&samsung->debug.data.d2);
|
||||
debugfs_create_u8("d3", S_IRUGO | S_IWUSR, root,
|
||||
&samsung->debug.data.d3);
|
||||
debugfs_create_blob("data", S_IRUGO | S_IWUSR, root,
|
||||
&samsung->debug.data_wrapper);
|
||||
debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR, root,
|
||||
&samsung->debug.f0000_wrapper);
|
||||
debugfs_create_file("call", S_IFREG | S_IRUGO, root, samsung,
|
||||
&samsung_laptop_call_fops);
|
||||
debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR, root,
|
||||
&samsung->debug.sdiag_wrapper);
|
||||
}
|
||||
|
||||
static void samsung_sabi_exit(struct samsung_laptop *samsung)
|
||||
@@ -1741,9 +1702,7 @@ static int __init samsung_init(void)
|
||||
if (ret)
|
||||
goto error_lid_handling;
|
||||
|
||||
ret = samsung_debugfs_init(samsung);
|
||||
if (ret)
|
||||
goto error_debugfs;
|
||||
samsung_debugfs_init(samsung);
|
||||
|
||||
samsung->pm_nb.notifier_call = samsung_pm_notification;
|
||||
register_pm_notifier(&samsung->pm_nb);
|
||||
@@ -1751,8 +1710,6 @@ static int __init samsung_init(void)
|
||||
samsung_platform_device = samsung->platform_device;
|
||||
return ret;
|
||||
|
||||
error_debugfs:
|
||||
samsung_lid_handling_exit(samsung);
|
||||
error_lid_handling:
|
||||
samsung_leds_exit(samsung);
|
||||
error_leds:
|
||||
|
@@ -87,6 +87,22 @@ static const struct ts_dmi_data chuwi_hi10_air_data = {
|
||||
.properties = chuwi_hi10_air_props,
|
||||
};
|
||||
|
||||
static const struct property_entry chuwi_hi10_plus_props[] = {
|
||||
PROPERTY_ENTRY_U32("touchscreen-min-x", 0),
|
||||
PROPERTY_ENTRY_U32("touchscreen-min-y", 5),
|
||||
PROPERTY_ENTRY_U32("touchscreen-size-x", 1914),
|
||||
PROPERTY_ENTRY_U32("touchscreen-size-y", 1283),
|
||||
PROPERTY_ENTRY_STRING("firmware-name", "gsl1680-chuwi-hi10plus.fw"),
|
||||
PROPERTY_ENTRY_U32("silead,max-fingers", 10),
|
||||
PROPERTY_ENTRY_BOOL("silead,home-button"),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct ts_dmi_data chuwi_hi10_plus_data = {
|
||||
.acpi_name = "MSSL0017:00",
|
||||
.properties = chuwi_hi10_plus_props,
|
||||
};
|
||||
|
||||
static const struct property_entry chuwi_vi8_props[] = {
|
||||
PROPERTY_ENTRY_U32("touchscreen-min-x", 4),
|
||||
PROPERTY_ENTRY_U32("touchscreen-min-y", 6),
|
||||
@@ -597,10 +613,20 @@ static const struct dmi_system_id touchscreen_dmi_table[] = {
|
||||
/* Chuwi Hi10 Air */
|
||||
.driver_data = (void *)&chuwi_hi10_air_data,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"),
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "CHUWI INNOVATION AND TECHNOLOGY(SHENZHEN)CO.LTD"),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"),
|
||||
DMI_MATCH(DMI_PRODUCT_SKU, "P1W6_C109D_B"),
|
||||
},
|
||||
},
|
||||
{
|
||||
/* Chuwi Hi10 Plus (CWI527) */
|
||||
.driver_data = (void *)&chuwi_hi10_plus_data,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Hi10 plus tablet"),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"),
|
||||
},
|
||||
},
|
||||
{
|
||||
/* Chuwi Vi8 (CWI506) */
|
||||
.driver_data = (void *)&chuwi_vi8_data,
|
||||
|
@@ -46,7 +46,7 @@ read_bmof(struct file *filp, struct kobject *kobj,
|
||||
return count;
|
||||
}
|
||||
|
||||
static int wmi_bmof_probe(struct wmi_device *wdev)
|
||||
static int wmi_bmof_probe(struct wmi_device *wdev, const void *context)
|
||||
{
|
||||
struct bmof_priv *priv;
|
||||
int ret;
|
||||
|
@@ -129,6 +129,28 @@ static bool find_guid(const char *guid_string, struct wmi_block **out)
|
||||
return false;
|
||||
}
|
||||
|
||||
static const void *find_guid_context(struct wmi_block *wblock,
|
||||
struct wmi_driver *wdriver)
|
||||
{
|
||||
const struct wmi_device_id *id;
|
||||
uuid_le guid_input;
|
||||
|
||||
if (wblock == NULL || wdriver == NULL)
|
||||
return NULL;
|
||||
if (wdriver->id_table == NULL)
|
||||
return NULL;
|
||||
|
||||
id = wdriver->id_table;
|
||||
while (*id->guid_string) {
|
||||
if (uuid_le_to_bin(id->guid_string, &guid_input))
|
||||
continue;
|
||||
if (!memcmp(wblock->gblock.guid, &guid_input, 16))
|
||||
return id->context;
|
||||
id++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int get_subobj_info(acpi_handle handle, const char *pathname,
|
||||
struct acpi_device_info **info)
|
||||
{
|
||||
@@ -618,6 +640,25 @@ bool wmi_has_guid(const char *guid_string)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wmi_has_guid);
|
||||
|
||||
/**
|
||||
* wmi_get_acpi_device_uid() - Get _UID name of ACPI device that defines GUID
|
||||
* @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
|
||||
*
|
||||
* Find the _UID of ACPI device associated with this WMI GUID.
|
||||
*
|
||||
* Return: The ACPI _UID field value or NULL if the WMI GUID was not found
|
||||
*/
|
||||
char *wmi_get_acpi_device_uid(const char *guid_string)
|
||||
{
|
||||
struct wmi_block *wblock = NULL;
|
||||
|
||||
if (!find_guid(guid_string, &wblock))
|
||||
return NULL;
|
||||
|
||||
return acpi_device_uid(wblock->acpi_device);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wmi_get_acpi_device_uid);
|
||||
|
||||
static struct wmi_block *dev_to_wblock(struct device *dev)
|
||||
{
|
||||
return container_of(dev, struct wmi_block, dev.dev);
|
||||
@@ -887,7 +928,8 @@ static int wmi_dev_probe(struct device *dev)
|
||||
dev_warn(dev, "failed to enable device -- probing anyway\n");
|
||||
|
||||
if (wdriver->probe) {
|
||||
ret = wdriver->probe(dev_to_wdev(dev));
|
||||
ret = wdriver->probe(dev_to_wdev(dev),
|
||||
find_guid_context(wblock, wdriver));
|
||||
if (ret != 0)
|
||||
goto probe_failure;
|
||||
}
|
||||
|
92
drivers/platform/x86/xiaomi-wmi.c
Arquivo normal
92
drivers/platform/x86/xiaomi-wmi.c
Arquivo normal
@@ -0,0 +1,92 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* WMI driver for Xiaomi Laptops */
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/wmi.h>
|
||||
|
||||
#include <uapi/linux/input-event-codes.h>
|
||||
|
||||
#define XIAOMI_KEY_FN_ESC_0 "A2095CCE-0491-44E7-BA27-F8ED8F88AA86"
|
||||
#define XIAOMI_KEY_FN_ESC_1 "7BBE8E39-B486-473D-BA13-66F75C5805CD"
|
||||
#define XIAOMI_KEY_FN_FN "409B028D-F06B-4C7C-8BBB-EE133A6BD87E"
|
||||
#define XIAOMI_KEY_CAPSLOCK "83FE7607-053A-4644-822A-21532C621FC7"
|
||||
#define XIAOMI_KEY_FN_F7 "76E9027C-95D0-4180-8692-DA6747DD1C2D"
|
||||
|
||||
#define XIAOMI_DEVICE(guid, key) \
|
||||
.guid_string = (guid), \
|
||||
.context = &(const unsigned int){key}
|
||||
|
||||
struct xiaomi_wmi {
|
||||
struct input_dev *input_dev;
|
||||
unsigned int key_code;
|
||||
};
|
||||
|
||||
int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context)
|
||||
{
|
||||
struct xiaomi_wmi *data;
|
||||
|
||||
if (wdev == NULL || context == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
data = devm_kzalloc(&wdev->dev, sizeof(struct xiaomi_wmi), GFP_KERNEL);
|
||||
if (data == NULL)
|
||||
return -ENOMEM;
|
||||
dev_set_drvdata(&wdev->dev, data);
|
||||
|
||||
data->input_dev = devm_input_allocate_device(&wdev->dev);
|
||||
if (data->input_dev == NULL)
|
||||
return -ENOMEM;
|
||||
data->input_dev->name = "Xiaomi WMI keys";
|
||||
data->input_dev->phys = "wmi/input0";
|
||||
|
||||
data->key_code = *((const unsigned int *)context);
|
||||
set_bit(EV_KEY, data->input_dev->evbit);
|
||||
set_bit(data->key_code, data->input_dev->keybit);
|
||||
|
||||
return input_register_device(data->input_dev);
|
||||
}
|
||||
|
||||
void xiaomi_wmi_notify(struct wmi_device *wdev, union acpi_object *dummy)
|
||||
{
|
||||
struct xiaomi_wmi *data;
|
||||
|
||||
if (wdev == NULL)
|
||||
return;
|
||||
|
||||
data = dev_get_drvdata(&wdev->dev);
|
||||
if (data == NULL)
|
||||
return;
|
||||
|
||||
input_report_key(data->input_dev, data->key_code, 1);
|
||||
input_sync(data->input_dev);
|
||||
input_report_key(data->input_dev, data->key_code, 0);
|
||||
input_sync(data->input_dev);
|
||||
}
|
||||
|
||||
static const struct wmi_device_id xiaomi_wmi_id_table[] = {
|
||||
// { XIAOMI_DEVICE(XIAOMI_KEY_FN_ESC_0, KEY_FN_ESC) },
|
||||
// { XIAOMI_DEVICE(XIAOMI_KEY_FN_ESC_1, KEY_FN_ESC) },
|
||||
{ XIAOMI_DEVICE(XIAOMI_KEY_FN_FN, KEY_PROG1) },
|
||||
// { XIAOMI_DEVICE(XIAOMI_KEY_CAPSLOCK, KEY_CAPSLOCK) },
|
||||
{ XIAOMI_DEVICE(XIAOMI_KEY_FN_F7, KEY_CUT) },
|
||||
|
||||
/* Terminating entry */
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct wmi_driver xiaomi_wmi_driver = {
|
||||
.driver = {
|
||||
.name = "xiaomi-wmi",
|
||||
},
|
||||
.id_table = xiaomi_wmi_id_table,
|
||||
.probe = xiaomi_wmi_probe,
|
||||
.notify = xiaomi_wmi_notify,
|
||||
};
|
||||
module_wmi_driver(xiaomi_wmi_driver);
|
||||
|
||||
MODULE_DEVICE_TABLE(wmi, xiaomi_wmi_id_table);
|
||||
MODULE_AUTHOR("Mattias Jacobsson");
|
||||
MODULE_DESCRIPTION("Xiaomi WMI driver");
|
||||
MODULE_LICENSE("GPL v2");
|
Referência em uma nova issue
Block a user