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:
Dmitry Torokhov
2019-08-11 23:24:46 -07:00
13375 arquivos alterados com 1034056 adições e 450998 exclusões

Ver arquivo

@@ -11,3 +11,5 @@ source "drivers/platform/goldfish/Kconfig"
source "drivers/platform/chrome/Kconfig"
source "drivers/platform/mellanox/Kconfig"
source "drivers/platform/olpc/Kconfig"

Ver arquivo

@@ -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/

Ver arquivo

@@ -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

Ver arquivo

@@ -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

Ver arquivo

@@ -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);

Ver arquivo

@@ -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:*");

Ver arquivo

@@ -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;

Ver arquivo

@@ -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);

Ver arquivo

@@ -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,

Ver arquivo

@@ -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 */

Ver arquivo

@@ -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 */

Ver arquivo

@@ -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(&params.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, &params.work);
flush_work(&params.work);
destroy_work_on_stack(&params.work);
kthread_queue_work(ec_spi->high_pri_worker, &params.work);
kthread_flush_work(&params.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,

Ver arquivo

@@ -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,
};

Ver arquivo

@@ -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,
};

Ver arquivo

@@ -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().

Ver arquivo

@@ -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

Ver arquivo

@@ -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);

Ver arquivo

@@ -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;

Ver arquivo

@@ -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);

Ver arquivo

@@ -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;
}

Ver arquivo

@@ -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);

Ver arquivo

@@ -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);
}

Ver arquivo

@@ -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
Ver arquivo

@@ -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

Ver arquivo

@@ -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

Ver arquivo

@@ -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)

Ver arquivo

@@ -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");

Ver arquivo

@@ -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

Ver arquivo

@@ -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/

Ver arquivo

@@ -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:

Ver arquivo

@@ -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 */

Ver arquivo

@@ -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);

Ver arquivo

@@ -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.
*/

Ver arquivo

@@ -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);

Ver arquivo

@@ -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);

Ver arquivo

@@ -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 },

Ver arquivo

@@ -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;

Ver arquivo

@@ -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;

Ver arquivo

@@ -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>

Ver arquivo

@@ -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),

Ver arquivo

@@ -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;

Ver arquivo

@@ -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;
}

Ver arquivo

@@ -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;

Ver arquivo

@@ -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;
}

Ver arquivo

@@ -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);

Ver arquivo

@@ -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");

Ver arquivo

@@ -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");

Ver arquivo

@@ -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");

Ver arquivo

@@ -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

Ver arquivo

@@ -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

Ver arquivo

@@ -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");

Ver arquivo

@@ -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

Ver arquivo

@@ -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");

Ver arquivo

@@ -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");

Ver arquivo

@@ -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");

Ver arquivo

@@ -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)

Ver arquivo

@@ -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;

Ver arquivo

@@ -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");

Ver arquivo

@@ -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);

Ver arquivo

@@ -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:

Ver arquivo

@@ -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,

Ver arquivo

@@ -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;

Ver arquivo

@@ -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;
}

Ver arquivo

@@ -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");