2019-01-29 10:38:18 +01:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
// LPC interface for ChromeOS Embedded Controller
|
|
|
|
//
|
|
|
|
// Copyright (C) 2012-2015 Google, Inc
|
|
|
|
//
|
|
|
|
// This driver uses the ChromeOS EC byte-level message-based protocol for
|
|
|
|
// communicating the keyboard state (which keys are pressed) from a keyboard EC
|
|
|
|
// to the AP over some bus (such as i2c, lpc, spi). The EC does debouncing,
|
|
|
|
// but everything else (including deghosting) is done here. The main
|
|
|
|
// motivation for this is to keep the EC firmware as simple as possible, since
|
|
|
|
// it cannot be easily upgraded and EC flash/IRAM space is relatively
|
|
|
|
// expensive.
|
2015-02-02 12:26:24 +01:00
|
|
|
|
2017-05-16 17:46:48 +02:00
|
|
|
#include <linux/acpi.h>
|
2015-02-02 12:26:24 +01:00
|
|
|
#include <linux/dmi.h>
|
|
|
|
#include <linux/delay.h>
|
2015-02-27 06:37:48 +01:00
|
|
|
#include <linux/io.h>
|
2018-10-09 10:56:46 -07:00
|
|
|
#include <linux/interrupt.h>
|
2023-05-09 23:26:24 +00:00
|
|
|
#include <linux/kobject.h>
|
2015-02-02 12:26:24 +01:00
|
|
|
#include <linux/module.h>
|
2019-09-02 11:53:05 +02:00
|
|
|
#include <linux/platform_data/cros_ec_commands.h>
|
|
|
|
#include <linux/platform_data/cros_ec_proto.h>
|
2015-02-02 12:26:24 +01:00
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/printk.h>
|
2023-01-04 01:15:24 +00:00
|
|
|
#include <linux/reboot.h>
|
2018-02-22 13:21:17 -08:00
|
|
|
#include <linux/suspend.h>
|
2015-02-02 12:26:24 +01:00
|
|
|
|
platform/chrome: cros_ec: Match implementation with headers
The 'cros_ec' core driver is the common interface for the cros_ec
transport drivers to do the shared operations to register, unregister,
suspend, resume and handle_event. The interface is provided by including
the header 'include/linux/platform_data/cros_ec_proto.h', however, instead
of have the implementation of these functions in cros_ec_proto.c, it is in
'cros_ec.c', which is a different kernel module. Apart from being a bad
practice, this can induce confusions allowing the users of the cros_ec
protocol to call these functions.
The register, unregister, suspend, resume and handle_event functions
*should* only be called by the different transport drivers (i2c, spi, lpc,
etc.), so make this a bit less confusing by moving these functions from
the public in-kernel space to a private include in platform/chrome, and
then, the interface for cros_ec module and for the cros_ec_proto module is
clean.
Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Signed-off-by: Benson Leung <bleung@chromium.org>
2020-01-22 10:07:01 +01:00
|
|
|
#include "cros_ec.h"
|
2019-06-14 23:43:00 +02:00
|
|
|
#include "cros_ec_lpc_mec.h"
|
2018-07-18 18:09:55 +02:00
|
|
|
|
2017-05-16 17:46:48 +02:00
|
|
|
#define DRV_NAME "cros_ec_lpcs"
|
2017-05-16 17:46:48 +02:00
|
|
|
#define ACPI_DRV_NAME "GOOG0004"
|
2025-01-29 02:13:29 +08:00
|
|
|
#define FRMW_ACPI_DRV_NAME "FRMWC004"
|
2015-02-02 12:26:24 +01:00
|
|
|
|
2017-12-01 14:42:21 +01:00
|
|
|
/* True if ACPI device is present */
|
|
|
|
static bool cros_ec_lpc_acpi_device_found;
|
|
|
|
|
2024-04-02 19:47:12 -05:00
|
|
|
/*
|
|
|
|
* Indicates that lpc_driver_data.quirk_mmio_memory_base should
|
|
|
|
* be used as the base port for EC mapped memory.
|
|
|
|
*/
|
|
|
|
#define CROS_EC_LPC_QUIRK_REMAP_MEMORY BIT(0)
|
2024-06-05 07:33:49 +01:00
|
|
|
/*
|
|
|
|
* Indicates that lpc_driver_data.quirk_acpi_id should be used to find
|
|
|
|
* the ACPI device.
|
|
|
|
*/
|
|
|
|
#define CROS_EC_LPC_QUIRK_ACPI_ID BIT(1)
|
2024-06-05 07:33:50 +01:00
|
|
|
/*
|
|
|
|
* Indicates that lpc_driver_data.quirk_aml_mutex_name should be used
|
|
|
|
* to find an AML mutex to protect access to Microchip EC.
|
|
|
|
*/
|
|
|
|
#define CROS_EC_LPC_QUIRK_AML_MUTEX BIT(2)
|
2024-04-02 19:47:12 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* struct lpc_driver_data - driver data attached to a DMI device ID to indicate
|
|
|
|
* hardware quirks.
|
|
|
|
* @quirks: a bitfield composed of quirks from CROS_EC_LPC_QUIRK_*
|
|
|
|
* @quirk_mmio_memory_base: The first I/O port addressing EC mapped memory (used
|
|
|
|
* when quirk ...REMAP_MEMORY is set.)
|
2024-06-05 07:33:49 +01:00
|
|
|
* @quirk_acpi_id: An ACPI HID to be used to find the ACPI device.
|
2024-06-05 07:33:50 +01:00
|
|
|
* @quirk_aml_mutex_name: The name of an AML mutex to be used to protect access
|
|
|
|
* to Microchip EC.
|
2024-04-02 19:47:12 -05:00
|
|
|
*/
|
|
|
|
struct lpc_driver_data {
|
|
|
|
u32 quirks;
|
|
|
|
u16 quirk_mmio_memory_base;
|
2024-06-05 07:33:49 +01:00
|
|
|
const char *quirk_acpi_id;
|
2024-06-05 07:33:50 +01:00
|
|
|
const char *quirk_aml_mutex_name;
|
2024-04-02 19:47:12 -05:00
|
|
|
};
|
|
|
|
|
2024-04-02 19:47:10 -05:00
|
|
|
/**
|
|
|
|
* struct cros_ec_lpc - LPC device-specific data
|
|
|
|
* @mmio_memory_base: The first I/O port addressing EC mapped memory.
|
2025-01-07 09:45:14 -08:00
|
|
|
* @base: For EC supporting memory mapping, base address of the mapped region.
|
|
|
|
* @mem32: Information about the memory mapped register region, if present.
|
2024-06-05 07:33:47 +01:00
|
|
|
* @read: Copy length bytes from EC address offset into buffer dest.
|
|
|
|
* Returns a negative error code on error, or the 8-bit checksum
|
|
|
|
* of all bytes read.
|
|
|
|
* @write: Copy length bytes from buffer msg into EC address offset.
|
|
|
|
* Returns a negative error code on error, or the 8-bit checksum
|
|
|
|
* of all bytes written.
|
platform/chrome: cros_ec_lpc: Choose Microchip EC at runtime
On many boards, communication between the kernel and the Embedded
Controller happens over an LPC bus. In these cases, the kernel config
CONFIG_CROS_EC_LPC is enabled. Some of these LPC boards contain a
Microchip Embedded Controller (MEC) that is different from the regular
EC. On these devices, the same LPC bus is used, but the protocol is
a little different. In these cases, the CONFIG_CROS_EC_LPC_MEC kernel
config is enabled. Currently, the kernel decides at compile-time whether
or not to use the MEC variant, and, when that kernel option is selected
it breaks the other boards. We would like a kind of runtime detection to
avoid this.
This patch adds that detection mechanism by probing the protocol at
runtime, first we assume that a MEC variant is connected, and if the
protocol fails it fallbacks to the regular EC. This adds a bit of
overload because we try to read twice on those LPC boards that doesn't
contain a MEC variant, but is a better solution than having to select the
EC variant at compile-time.
While here also fix the alignment in Kconfig file for this config option
replacing the spaces by tabs.
Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Reviewed-by: Ezequiel Garcia <ezequiel@collabora.com>
Tested-by: Nick Crews <ncrews@chromium.org>
Reviewed-by: Nick Crews <ncrews@chromium.org>
2019-06-14 23:43:01 +02:00
|
|
|
*/
|
2025-01-07 09:45:13 -08:00
|
|
|
struct cros_ec_lpc {
|
|
|
|
u16 mmio_memory_base;
|
2025-01-07 09:45:14 -08:00
|
|
|
void __iomem *base;
|
|
|
|
struct acpi_resource_fixed_memory32 mem32;
|
2025-01-07 09:45:13 -08:00
|
|
|
int (*read)(struct cros_ec_lpc *ec_lpc, unsigned int offset,
|
|
|
|
unsigned int length, u8 *dest);
|
|
|
|
int (*write)(struct cros_ec_lpc *ec_lpc, unsigned int offset,
|
|
|
|
unsigned int length, const u8 *msg);
|
platform/chrome: cros_ec_lpc: Choose Microchip EC at runtime
On many boards, communication between the kernel and the Embedded
Controller happens over an LPC bus. In these cases, the kernel config
CONFIG_CROS_EC_LPC is enabled. Some of these LPC boards contain a
Microchip Embedded Controller (MEC) that is different from the regular
EC. On these devices, the same LPC bus is used, but the protocol is
a little different. In these cases, the CONFIG_CROS_EC_LPC_MEC kernel
config is enabled. Currently, the kernel decides at compile-time whether
or not to use the MEC variant, and, when that kernel option is selected
it breaks the other boards. We would like a kind of runtime detection to
avoid this.
This patch adds that detection mechanism by probing the protocol at
runtime, first we assume that a MEC variant is connected, and if the
protocol fails it fallbacks to the regular EC. This adds a bit of
overload because we try to read twice on those LPC boards that doesn't
contain a MEC variant, but is a better solution than having to select the
EC variant at compile-time.
While here also fix the alignment in Kconfig file for this config option
replacing the spaces by tabs.
Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Reviewed-by: Ezequiel Garcia <ezequiel@collabora.com>
Tested-by: Nick Crews <ncrews@chromium.org>
Reviewed-by: Nick Crews <ncrews@chromium.org>
2019-06-14 23:43:01 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A generic instance of the read function of struct lpc_driver_ops, used for
|
|
|
|
* the LPC EC.
|
|
|
|
*/
|
2025-01-07 09:45:13 -08:00
|
|
|
static int cros_ec_lpc_read_bytes(struct cros_ec_lpc *_, unsigned int offset, unsigned int length,
|
2024-06-05 07:33:47 +01:00
|
|
|
u8 *dest)
|
2019-06-14 23:43:00 +02:00
|
|
|
{
|
2024-06-05 07:33:47 +01:00
|
|
|
u8 sum = 0;
|
2019-06-14 23:43:00 +02:00
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < length; ++i) {
|
|
|
|
dest[i] = inb(offset + i);
|
|
|
|
sum += dest[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return checksum of all bytes read */
|
|
|
|
return sum;
|
|
|
|
}
|
|
|
|
|
platform/chrome: cros_ec_lpc: Choose Microchip EC at runtime
On many boards, communication between the kernel and the Embedded
Controller happens over an LPC bus. In these cases, the kernel config
CONFIG_CROS_EC_LPC is enabled. Some of these LPC boards contain a
Microchip Embedded Controller (MEC) that is different from the regular
EC. On these devices, the same LPC bus is used, but the protocol is
a little different. In these cases, the CONFIG_CROS_EC_LPC_MEC kernel
config is enabled. Currently, the kernel decides at compile-time whether
or not to use the MEC variant, and, when that kernel option is selected
it breaks the other boards. We would like a kind of runtime detection to
avoid this.
This patch adds that detection mechanism by probing the protocol at
runtime, first we assume that a MEC variant is connected, and if the
protocol fails it fallbacks to the regular EC. This adds a bit of
overload because we try to read twice on those LPC boards that doesn't
contain a MEC variant, but is a better solution than having to select the
EC variant at compile-time.
While here also fix the alignment in Kconfig file for this config option
replacing the spaces by tabs.
Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Reviewed-by: Ezequiel Garcia <ezequiel@collabora.com>
Tested-by: Nick Crews <ncrews@chromium.org>
Reviewed-by: Nick Crews <ncrews@chromium.org>
2019-06-14 23:43:01 +02:00
|
|
|
/*
|
|
|
|
* A generic instance of the write function of struct lpc_driver_ops, used for
|
|
|
|
* the LPC EC.
|
|
|
|
*/
|
2025-01-07 09:45:13 -08:00
|
|
|
static int cros_ec_lpc_write_bytes(struct cros_ec_lpc *_, unsigned int offset, unsigned int length,
|
2024-06-05 07:33:47 +01:00
|
|
|
const u8 *msg)
|
2019-06-14 23:43:00 +02:00
|
|
|
{
|
2024-06-05 07:33:47 +01:00
|
|
|
u8 sum = 0;
|
2019-06-14 23:43:00 +02:00
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < length; ++i) {
|
|
|
|
outb(msg[i], offset + i);
|
|
|
|
sum += msg[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return checksum of all bytes written */
|
|
|
|
return sum;
|
|
|
|
}
|
|
|
|
|
platform/chrome: cros_ec_lpc: Choose Microchip EC at runtime
On many boards, communication between the kernel and the Embedded
Controller happens over an LPC bus. In these cases, the kernel config
CONFIG_CROS_EC_LPC is enabled. Some of these LPC boards contain a
Microchip Embedded Controller (MEC) that is different from the regular
EC. On these devices, the same LPC bus is used, but the protocol is
a little different. In these cases, the CONFIG_CROS_EC_LPC_MEC kernel
config is enabled. Currently, the kernel decides at compile-time whether
or not to use the MEC variant, and, when that kernel option is selected
it breaks the other boards. We would like a kind of runtime detection to
avoid this.
This patch adds that detection mechanism by probing the protocol at
runtime, first we assume that a MEC variant is connected, and if the
protocol fails it fallbacks to the regular EC. This adds a bit of
overload because we try to read twice on those LPC boards that doesn't
contain a MEC variant, but is a better solution than having to select the
EC variant at compile-time.
While here also fix the alignment in Kconfig file for this config option
replacing the spaces by tabs.
Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Reviewed-by: Ezequiel Garcia <ezequiel@collabora.com>
Tested-by: Nick Crews <ncrews@chromium.org>
Reviewed-by: Nick Crews <ncrews@chromium.org>
2019-06-14 23:43:01 +02:00
|
|
|
/*
|
|
|
|
* An instance of the read function of struct lpc_driver_ops, used for the
|
|
|
|
* MEC variant of LPC EC.
|
|
|
|
*/
|
2025-01-07 09:45:13 -08:00
|
|
|
static int cros_ec_lpc_mec_read_bytes(struct cros_ec_lpc *ec_lpc, unsigned int offset,
|
|
|
|
unsigned int length, u8 *dest)
|
2019-06-14 23:43:00 +02:00
|
|
|
{
|
|
|
|
int in_range = cros_ec_lpc_mec_in_range(offset, length);
|
|
|
|
|
|
|
|
if (in_range < 0)
|
2024-06-13 16:55:14 +03:00
|
|
|
return in_range;
|
2019-06-14 23:43:00 +02:00
|
|
|
|
|
|
|
return in_range ?
|
|
|
|
cros_ec_lpc_io_bytes_mec(MEC_IO_READ,
|
|
|
|
offset - EC_HOST_CMD_REGION0,
|
|
|
|
length, dest) :
|
2025-01-07 09:45:13 -08:00
|
|
|
cros_ec_lpc_read_bytes(ec_lpc, offset, length, dest);
|
2019-06-14 23:43:00 +02:00
|
|
|
}
|
|
|
|
|
platform/chrome: cros_ec_lpc: Choose Microchip EC at runtime
On many boards, communication between the kernel and the Embedded
Controller happens over an LPC bus. In these cases, the kernel config
CONFIG_CROS_EC_LPC is enabled. Some of these LPC boards contain a
Microchip Embedded Controller (MEC) that is different from the regular
EC. On these devices, the same LPC bus is used, but the protocol is
a little different. In these cases, the CONFIG_CROS_EC_LPC_MEC kernel
config is enabled. Currently, the kernel decides at compile-time whether
or not to use the MEC variant, and, when that kernel option is selected
it breaks the other boards. We would like a kind of runtime detection to
avoid this.
This patch adds that detection mechanism by probing the protocol at
runtime, first we assume that a MEC variant is connected, and if the
protocol fails it fallbacks to the regular EC. This adds a bit of
overload because we try to read twice on those LPC boards that doesn't
contain a MEC variant, but is a better solution than having to select the
EC variant at compile-time.
While here also fix the alignment in Kconfig file for this config option
replacing the spaces by tabs.
Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Reviewed-by: Ezequiel Garcia <ezequiel@collabora.com>
Tested-by: Nick Crews <ncrews@chromium.org>
Reviewed-by: Nick Crews <ncrews@chromium.org>
2019-06-14 23:43:01 +02:00
|
|
|
/*
|
|
|
|
* An instance of the write function of struct lpc_driver_ops, used for the
|
|
|
|
* MEC variant of LPC EC.
|
|
|
|
*/
|
2025-01-07 09:45:13 -08:00
|
|
|
static int cros_ec_lpc_mec_write_bytes(struct cros_ec_lpc *ec_lpc, unsigned int offset,
|
|
|
|
unsigned int length, const u8 *msg)
|
2019-06-14 23:43:00 +02:00
|
|
|
{
|
|
|
|
int in_range = cros_ec_lpc_mec_in_range(offset, length);
|
|
|
|
|
|
|
|
if (in_range < 0)
|
2024-06-13 16:55:14 +03:00
|
|
|
return in_range;
|
2019-06-14 23:43:00 +02:00
|
|
|
|
|
|
|
return in_range ?
|
|
|
|
cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE,
|
|
|
|
offset - EC_HOST_CMD_REGION0,
|
platform/chrome: cros_ec_lpc: Choose Microchip EC at runtime
On many boards, communication between the kernel and the Embedded
Controller happens over an LPC bus. In these cases, the kernel config
CONFIG_CROS_EC_LPC is enabled. Some of these LPC boards contain a
Microchip Embedded Controller (MEC) that is different from the regular
EC. On these devices, the same LPC bus is used, but the protocol is
a little different. In these cases, the CONFIG_CROS_EC_LPC_MEC kernel
config is enabled. Currently, the kernel decides at compile-time whether
or not to use the MEC variant, and, when that kernel option is selected
it breaks the other boards. We would like a kind of runtime detection to
avoid this.
This patch adds that detection mechanism by probing the protocol at
runtime, first we assume that a MEC variant is connected, and if the
protocol fails it fallbacks to the regular EC. This adds a bit of
overload because we try to read twice on those LPC boards that doesn't
contain a MEC variant, but is a better solution than having to select the
EC variant at compile-time.
While here also fix the alignment in Kconfig file for this config option
replacing the spaces by tabs.
Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Reviewed-by: Ezequiel Garcia <ezequiel@collabora.com>
Tested-by: Nick Crews <ncrews@chromium.org>
Reviewed-by: Nick Crews <ncrews@chromium.org>
2019-06-14 23:43:01 +02:00
|
|
|
length, (u8 *)msg) :
|
2025-01-07 09:45:13 -08:00
|
|
|
cros_ec_lpc_write_bytes(ec_lpc, offset, length, msg);
|
|
|
|
}
|
2025-01-07 09:45:14 -08:00
|
|
|
|
|
|
|
static int cros_ec_lpc_direct_read(struct cros_ec_lpc *ec_lpc, unsigned int offset,
|
|
|
|
unsigned int length, u8 *dest)
|
|
|
|
{
|
|
|
|
int sum = 0;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (offset < EC_HOST_CMD_REGION0 || offset > EC_LPC_ADDR_MEMMAP +
|
|
|
|
EC_MEMMAP_SIZE) {
|
|
|
|
return cros_ec_lpc_read_bytes(ec_lpc, offset, length, dest);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < length; ++i) {
|
|
|
|
dest[i] = readb(ec_lpc->base + offset - EC_HOST_CMD_REGION0 + i);
|
|
|
|
sum += dest[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return checksum of all bytes read */
|
|
|
|
return sum;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cros_ec_lpc_direct_write(struct cros_ec_lpc *ec_lpc, unsigned int offset,
|
|
|
|
unsigned int length, const u8 *msg)
|
|
|
|
{
|
|
|
|
int sum = 0;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (offset < EC_HOST_CMD_REGION0 || offset > EC_LPC_ADDR_MEMMAP +
|
|
|
|
EC_MEMMAP_SIZE) {
|
|
|
|
return cros_ec_lpc_write_bytes(ec_lpc, offset, length, msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < length; ++i) {
|
|
|
|
writeb(msg[i], ec_lpc->base + offset - EC_HOST_CMD_REGION0 + i);
|
|
|
|
sum += msg[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return checksum of all bytes written */
|
|
|
|
return sum;
|
2019-06-14 23:43:00 +02:00
|
|
|
}
|
|
|
|
|
2025-01-07 09:45:13 -08:00
|
|
|
static int ec_response_timed_out(struct cros_ec_lpc *ec_lpc)
|
2015-02-02 12:26:24 +01:00
|
|
|
{
|
|
|
|
unsigned long one_second = jiffies + HZ;
|
2017-05-16 17:46:48 +02:00
|
|
|
u8 data;
|
2024-06-05 07:33:47 +01:00
|
|
|
int ret;
|
2015-02-02 12:26:24 +01:00
|
|
|
|
|
|
|
usleep_range(200, 300);
|
|
|
|
do {
|
2025-01-07 09:45:13 -08:00
|
|
|
ret = ec_lpc->read(ec_lpc, EC_LPC_ADDR_HOST_CMD, 1, &data);
|
2024-06-05 07:33:47 +01:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
if (!(data & EC_LPC_STATUS_BUSY_MASK))
|
2015-02-02 12:26:24 +01:00
|
|
|
return 0;
|
|
|
|
usleep_range(100, 200);
|
|
|
|
} while (time_before(jiffies, one_second));
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2015-06-09 13:04:46 +02:00
|
|
|
static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec,
|
|
|
|
struct cros_ec_command *msg)
|
|
|
|
{
|
2025-01-07 09:45:13 -08:00
|
|
|
struct cros_ec_lpc *ec_lpc = ec->priv;
|
2015-06-09 13:04:46 +02:00
|
|
|
struct ec_host_response response;
|
2017-05-16 17:46:48 +02:00
|
|
|
u8 sum;
|
2015-06-09 13:04:46 +02:00
|
|
|
int ret = 0;
|
|
|
|
u8 *dout;
|
|
|
|
|
|
|
|
ret = cros_ec_prepare_tx(ec, msg);
|
2022-05-13 12:41:38 +08:00
|
|
|
if (ret < 0)
|
|
|
|
goto done;
|
2015-06-09 13:04:46 +02:00
|
|
|
|
|
|
|
/* Write buffer */
|
2025-01-07 09:45:13 -08:00
|
|
|
ret = ec_lpc->write(ec_lpc, EC_LPC_ADDR_HOST_PACKET, ret, ec->dout);
|
2024-06-05 07:33:47 +01:00
|
|
|
if (ret < 0)
|
|
|
|
goto done;
|
2015-06-09 13:04:46 +02:00
|
|
|
|
|
|
|
/* Here we go */
|
2017-05-16 17:46:48 +02:00
|
|
|
sum = EC_COMMAND_PROTOCOL_3;
|
2025-01-07 09:45:13 -08:00
|
|
|
ret = ec_lpc->write(ec_lpc, EC_LPC_ADDR_HOST_CMD, 1, &sum);
|
2024-06-05 07:33:47 +01:00
|
|
|
if (ret < 0)
|
|
|
|
goto done;
|
2015-06-09 13:04:46 +02:00
|
|
|
|
2025-01-07 09:45:13 -08:00
|
|
|
ret = ec_response_timed_out(ec_lpc);
|
2024-06-05 07:33:47 +01:00
|
|
|
if (ret < 0)
|
|
|
|
goto done;
|
|
|
|
if (ret) {
|
2021-09-25 18:17:43 +01:00
|
|
|
dev_warn(ec->dev, "EC response timed out\n");
|
2015-06-09 13:04:46 +02:00
|
|
|
ret = -EIO;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check result */
|
2025-01-07 09:45:13 -08:00
|
|
|
ret = ec_lpc->read(ec_lpc, EC_LPC_ADDR_HOST_DATA, 1, &sum);
|
2024-06-05 07:33:47 +01:00
|
|
|
if (ret < 0)
|
|
|
|
goto done;
|
|
|
|
msg->result = ret;
|
2015-06-09 13:04:46 +02:00
|
|
|
ret = cros_ec_check_result(ec, msg);
|
|
|
|
if (ret)
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
/* Read back response */
|
|
|
|
dout = (u8 *)&response;
|
2025-01-07 09:45:13 -08:00
|
|
|
ret = ec_lpc->read(ec_lpc, EC_LPC_ADDR_HOST_PACKET, sizeof(response),
|
platform/chrome: cros_ec_lpc: Choose Microchip EC at runtime
On many boards, communication between the kernel and the Embedded
Controller happens over an LPC bus. In these cases, the kernel config
CONFIG_CROS_EC_LPC is enabled. Some of these LPC boards contain a
Microchip Embedded Controller (MEC) that is different from the regular
EC. On these devices, the same LPC bus is used, but the protocol is
a little different. In these cases, the CONFIG_CROS_EC_LPC_MEC kernel
config is enabled. Currently, the kernel decides at compile-time whether
or not to use the MEC variant, and, when that kernel option is selected
it breaks the other boards. We would like a kind of runtime detection to
avoid this.
This patch adds that detection mechanism by probing the protocol at
runtime, first we assume that a MEC variant is connected, and if the
protocol fails it fallbacks to the regular EC. This adds a bit of
overload because we try to read twice on those LPC boards that doesn't
contain a MEC variant, but is a better solution than having to select the
EC variant at compile-time.
While here also fix the alignment in Kconfig file for this config option
replacing the spaces by tabs.
Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Reviewed-by: Ezequiel Garcia <ezequiel@collabora.com>
Tested-by: Nick Crews <ncrews@chromium.org>
Reviewed-by: Nick Crews <ncrews@chromium.org>
2019-06-14 23:43:01 +02:00
|
|
|
dout);
|
2024-06-05 07:33:47 +01:00
|
|
|
if (ret < 0)
|
|
|
|
goto done;
|
|
|
|
sum = ret;
|
2015-06-09 13:04:46 +02:00
|
|
|
|
|
|
|
msg->result = response.result;
|
|
|
|
|
|
|
|
if (response.data_len > msg->insize) {
|
|
|
|
dev_err(ec->dev,
|
|
|
|
"packet too long (%d bytes, expected %d)",
|
|
|
|
response.data_len, msg->insize);
|
|
|
|
ret = -EMSGSIZE;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Read response and process checksum */
|
2025-01-07 09:45:13 -08:00
|
|
|
ret = ec_lpc->read(ec_lpc, EC_LPC_ADDR_HOST_PACKET +
|
2024-06-05 07:33:47 +01:00
|
|
|
sizeof(response), response.data_len,
|
|
|
|
msg->data);
|
|
|
|
if (ret < 0)
|
|
|
|
goto done;
|
|
|
|
sum += ret;
|
2015-06-09 13:04:46 +02:00
|
|
|
|
|
|
|
if (sum) {
|
|
|
|
dev_err(ec->dev,
|
|
|
|
"bad packet checksum %02x\n",
|
|
|
|
response.checksum);
|
|
|
|
ret = -EBADMSG;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return actual amount of data received */
|
|
|
|
ret = response.data_len;
|
|
|
|
done:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-02-02 12:26:24 +01:00
|
|
|
static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec,
|
|
|
|
struct cros_ec_command *msg)
|
|
|
|
{
|
2025-01-07 09:45:13 -08:00
|
|
|
struct cros_ec_lpc *ec_lpc = ec->priv;
|
2015-02-02 12:26:24 +01:00
|
|
|
struct ec_lpc_host_args args;
|
2017-05-16 17:46:48 +02:00
|
|
|
u8 sum;
|
2015-02-02 12:26:24 +01:00
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (msg->outsize > EC_PROTO2_MAX_PARAM_SIZE ||
|
|
|
|
msg->insize > EC_PROTO2_MAX_PARAM_SIZE) {
|
|
|
|
dev_err(ec->dev,
|
|
|
|
"invalid buffer sizes (out %d, in %d)\n",
|
|
|
|
msg->outsize, msg->insize);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now actually send the command to the EC and get the result */
|
|
|
|
args.flags = EC_HOST_ARGS_FLAG_FROM_HOST;
|
|
|
|
args.command_version = msg->version;
|
|
|
|
args.data_size = msg->outsize;
|
|
|
|
|
|
|
|
/* Initialize checksum */
|
2017-05-16 17:46:48 +02:00
|
|
|
sum = msg->command + args.flags + args.command_version + args.data_size;
|
2015-02-02 12:26:24 +01:00
|
|
|
|
|
|
|
/* Copy data and update checksum */
|
2025-01-07 09:45:13 -08:00
|
|
|
ret = ec_lpc->write(ec_lpc, EC_LPC_ADDR_HOST_PARAM, msg->outsize,
|
2024-06-05 07:33:47 +01:00
|
|
|
msg->data);
|
|
|
|
if (ret < 0)
|
|
|
|
goto done;
|
|
|
|
sum += ret;
|
2015-02-02 12:26:24 +01:00
|
|
|
|
|
|
|
/* Finalize checksum and write args */
|
2017-05-16 17:46:48 +02:00
|
|
|
args.checksum = sum;
|
2025-01-07 09:45:13 -08:00
|
|
|
ret = ec_lpc->write(ec_lpc, EC_LPC_ADDR_HOST_ARGS, sizeof(args),
|
2024-06-05 07:33:47 +01:00
|
|
|
(u8 *)&args);
|
|
|
|
if (ret < 0)
|
|
|
|
goto done;
|
2015-02-02 12:26:24 +01:00
|
|
|
|
|
|
|
/* Here we go */
|
2017-05-16 17:46:48 +02:00
|
|
|
sum = msg->command;
|
2025-01-07 09:45:13 -08:00
|
|
|
ret = ec_lpc->write(ec_lpc, EC_LPC_ADDR_HOST_CMD, 1, &sum);
|
2024-06-05 07:33:47 +01:00
|
|
|
if (ret < 0)
|
|
|
|
goto done;
|
2015-02-02 12:26:24 +01:00
|
|
|
|
2025-01-07 09:45:13 -08:00
|
|
|
ret = ec_response_timed_out(ec_lpc);
|
2024-06-05 07:33:47 +01:00
|
|
|
if (ret < 0)
|
|
|
|
goto done;
|
|
|
|
if (ret) {
|
2021-09-25 18:17:43 +01:00
|
|
|
dev_warn(ec->dev, "EC response timed out\n");
|
2015-02-02 12:26:24 +01:00
|
|
|
ret = -EIO;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check result */
|
2025-01-07 09:45:13 -08:00
|
|
|
ret = ec_lpc->read(ec_lpc, EC_LPC_ADDR_HOST_DATA, 1, &sum);
|
2024-06-05 07:33:47 +01:00
|
|
|
if (ret < 0)
|
|
|
|
goto done;
|
|
|
|
msg->result = ret;
|
2015-06-22 08:27:18 +02:00
|
|
|
ret = cros_ec_check_result(ec, msg);
|
|
|
|
if (ret)
|
2015-02-02 12:26:24 +01:00
|
|
|
goto done;
|
|
|
|
|
|
|
|
/* Read back args */
|
2025-01-07 09:45:13 -08:00
|
|
|
ret = ec_lpc->read(ec_lpc, EC_LPC_ADDR_HOST_ARGS, sizeof(args), (u8 *)&args);
|
2024-06-05 07:33:47 +01:00
|
|
|
if (ret < 0)
|
|
|
|
goto done;
|
2015-02-02 12:26:24 +01:00
|
|
|
|
|
|
|
if (args.data_size > msg->insize) {
|
|
|
|
dev_err(ec->dev,
|
|
|
|
"packet too long (%d bytes, expected %d)",
|
|
|
|
args.data_size, msg->insize);
|
|
|
|
ret = -ENOSPC;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Start calculating response checksum */
|
2017-05-16 17:46:48 +02:00
|
|
|
sum = msg->command + args.flags + args.command_version + args.data_size;
|
2015-02-02 12:26:24 +01:00
|
|
|
|
|
|
|
/* Read response and update checksum */
|
2025-01-07 09:45:13 -08:00
|
|
|
ret = ec_lpc->read(ec_lpc, EC_LPC_ADDR_HOST_PARAM, args.data_size,
|
2024-06-05 07:33:47 +01:00
|
|
|
msg->data);
|
|
|
|
if (ret < 0)
|
|
|
|
goto done;
|
|
|
|
sum += ret;
|
2015-02-02 12:26:24 +01:00
|
|
|
|
|
|
|
/* Verify checksum */
|
2017-05-16 17:46:48 +02:00
|
|
|
if (args.checksum != sum) {
|
2015-02-02 12:26:24 +01:00
|
|
|
dev_err(ec->dev,
|
|
|
|
"bad packet checksum, expected %02x, got %02x\n",
|
2017-05-16 17:46:48 +02:00
|
|
|
args.checksum, sum);
|
2015-02-02 12:26:24 +01:00
|
|
|
ret = -EBADMSG;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return actual amount of data received */
|
|
|
|
ret = args.data_size;
|
|
|
|
done:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Returns num bytes read, or negative on error. Doesn't need locking. */
|
|
|
|
static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset,
|
|
|
|
unsigned int bytes, void *dest)
|
|
|
|
{
|
2024-04-02 19:47:10 -05:00
|
|
|
struct cros_ec_lpc *ec_lpc = ec->priv;
|
2015-02-02 12:26:24 +01:00
|
|
|
int i = offset;
|
|
|
|
char *s = dest;
|
|
|
|
int cnt = 0;
|
2024-06-05 07:33:47 +01:00
|
|
|
int ret;
|
2015-02-02 12:26:24 +01:00
|
|
|
|
|
|
|
if (offset >= EC_MEMMAP_SIZE - bytes)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* fixed length */
|
|
|
|
if (bytes) {
|
2025-01-07 09:45:13 -08:00
|
|
|
ret = ec_lpc->read(ec_lpc, ec_lpc->mmio_memory_base + offset, bytes, s);
|
2024-06-05 07:33:47 +01:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
2017-05-16 17:46:48 +02:00
|
|
|
return bytes;
|
2015-02-02 12:26:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* string */
|
|
|
|
for (; i < EC_MEMMAP_SIZE; i++, s++) {
|
2025-01-07 09:45:13 -08:00
|
|
|
ret = ec_lpc->read(ec_lpc, ec_lpc->mmio_memory_base + i, 1, s);
|
2024-06-05 07:33:47 +01:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
2015-02-02 12:26:24 +01:00
|
|
|
cnt++;
|
|
|
|
if (!*s)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return cnt;
|
|
|
|
}
|
|
|
|
|
2017-05-16 17:46:48 +02:00
|
|
|
static void cros_ec_lpc_acpi_notify(acpi_handle device, u32 value, void *data)
|
|
|
|
{
|
2023-05-09 23:26:24 +00:00
|
|
|
static const char *env[] = { "ERROR=PANIC", NULL };
|
2017-05-16 17:46:48 +02:00
|
|
|
struct cros_ec_device *ec_dev = data;
|
2019-11-19 13:45:46 +01:00
|
|
|
bool ec_has_more_events;
|
|
|
|
int ret;
|
2017-05-16 17:46:48 +02:00
|
|
|
|
2019-11-19 13:45:46 +01:00
|
|
|
ec_dev->last_event_time = cros_ec_get_time_ns();
|
|
|
|
|
2023-01-04 01:15:23 +00:00
|
|
|
if (value == ACPI_NOTIFY_CROS_EC_PANIC) {
|
|
|
|
dev_emerg(ec_dev->dev, "CrOS EC Panic Reported. Shutdown is imminent!");
|
|
|
|
blocking_notifier_call_chain(&ec_dev->panic_notifier, 0, ec_dev);
|
2023-05-09 23:26:24 +00:00
|
|
|
kobject_uevent_env(&ec_dev->dev->kobj, KOBJ_CHANGE, (char **)env);
|
2023-08-02 17:58:48 +00:00
|
|
|
/* Begin orderly shutdown. EC will force reset after a short period. */
|
2025-02-17 21:39:49 +01:00
|
|
|
__hw_protection_trigger("CrOS EC Panic", -1, HWPROT_ACT_SHUTDOWN);
|
2023-01-04 01:15:23 +00:00
|
|
|
/* Do not query for other events after a panic is reported */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-12-18 01:57:59 +00:00
|
|
|
if (value == ACPI_NOTIFY_CROS_EC_MKBP && ec_dev->mkbp_event_supported)
|
2019-11-19 13:45:46 +01:00
|
|
|
do {
|
|
|
|
ret = cros_ec_get_next_event(ec_dev, NULL,
|
|
|
|
&ec_has_more_events);
|
|
|
|
if (ret > 0)
|
|
|
|
blocking_notifier_call_chain(
|
|
|
|
&ec_dev->event_notifier, 0,
|
|
|
|
ec_dev);
|
|
|
|
} while (ec_has_more_events);
|
2018-02-22 13:21:17 -08:00
|
|
|
|
|
|
|
if (value == ACPI_NOTIFY_DEVICE_WAKE)
|
|
|
|
pm_system_wakeup();
|
2017-05-16 17:46:48 +02:00
|
|
|
}
|
|
|
|
|
2024-06-05 07:33:49 +01:00
|
|
|
static acpi_status cros_ec_lpc_parse_device(acpi_handle handle, u32 level,
|
|
|
|
void *context, void **retval)
|
|
|
|
{
|
|
|
|
*(struct acpi_device **)context = acpi_fetch_acpi_dev(handle);
|
|
|
|
return AE_CTRL_TERMINATE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct acpi_device *cros_ec_lpc_get_device(const char *id)
|
|
|
|
{
|
|
|
|
struct acpi_device *adev = NULL;
|
|
|
|
acpi_status status = acpi_get_devices(id, cros_ec_lpc_parse_device,
|
|
|
|
&adev, NULL);
|
|
|
|
if (ACPI_FAILURE(status)) {
|
|
|
|
pr_warn(DRV_NAME ": Looking for %s failed\n", id);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return adev;
|
|
|
|
}
|
|
|
|
|
2025-01-07 09:45:14 -08:00
|
|
|
static acpi_status cros_ec_lpc_resources(struct acpi_resource *res, void *data)
|
|
|
|
{
|
|
|
|
struct cros_ec_lpc *ec_lpc = data;
|
|
|
|
|
|
|
|
switch (res->type) {
|
|
|
|
case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
|
|
|
|
ec_lpc->mem32 = res->data.fixed_memory32;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return AE_OK;
|
|
|
|
}
|
|
|
|
|
2015-02-02 12:26:24 +01:00
|
|
|
static int cros_ec_lpc_probe(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct device *dev = &pdev->dev;
|
2017-05-16 17:46:48 +02:00
|
|
|
struct acpi_device *adev;
|
|
|
|
acpi_status status;
|
2015-02-02 12:26:24 +01:00
|
|
|
struct cros_ec_device *ec_dev;
|
2024-04-02 19:47:10 -05:00
|
|
|
struct cros_ec_lpc *ec_lpc;
|
2025-01-29 02:13:29 +08:00
|
|
|
const struct lpc_driver_data *driver_data;
|
2023-01-10 14:36:11 -05:00
|
|
|
u8 buf[2] = {};
|
2018-10-09 10:56:46 -07:00
|
|
|
int irq, ret;
|
2024-04-02 19:47:12 -05:00
|
|
|
u32 quirks;
|
2015-02-02 12:26:24 +01:00
|
|
|
|
2024-04-02 19:47:10 -05:00
|
|
|
ec_lpc = devm_kzalloc(dev, sizeof(*ec_lpc), GFP_KERNEL);
|
|
|
|
if (!ec_lpc)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
ec_lpc->mmio_memory_base = EC_LPC_ADDR_MEMMAP;
|
|
|
|
|
2024-04-02 19:47:12 -05:00
|
|
|
driver_data = platform_get_drvdata(pdev);
|
2025-01-29 02:13:29 +08:00
|
|
|
if (!driver_data)
|
|
|
|
driver_data = acpi_device_get_match_data(dev);
|
|
|
|
|
2024-04-02 19:47:12 -05:00
|
|
|
if (driver_data) {
|
|
|
|
quirks = driver_data->quirks;
|
|
|
|
|
|
|
|
if (quirks)
|
|
|
|
dev_info(dev, "loaded with quirks %8.08x\n", quirks);
|
|
|
|
|
|
|
|
if (quirks & CROS_EC_LPC_QUIRK_REMAP_MEMORY)
|
|
|
|
ec_lpc->mmio_memory_base = driver_data->quirk_mmio_memory_base;
|
2024-06-05 07:33:49 +01:00
|
|
|
|
|
|
|
if (quirks & CROS_EC_LPC_QUIRK_ACPI_ID) {
|
|
|
|
adev = cros_ec_lpc_get_device(driver_data->quirk_acpi_id);
|
|
|
|
if (!adev) {
|
|
|
|
dev_err(dev, "failed to get ACPI device '%s'",
|
|
|
|
driver_data->quirk_acpi_id);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
ACPI_COMPANION_SET(dev, adev);
|
|
|
|
}
|
2024-06-05 07:33:50 +01:00
|
|
|
|
|
|
|
if (quirks & CROS_EC_LPC_QUIRK_AML_MUTEX) {
|
2025-01-07 09:45:13 -08:00
|
|
|
const char *name = driver_data->quirk_aml_mutex_name;
|
2024-06-05 07:33:50 +01:00
|
|
|
ret = cros_ec_lpc_mec_acpi_mutex(ACPI_COMPANION(dev), name);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "failed to get AML mutex '%s'", name);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
dev_info(dev, "got AML mutex '%s'", name);
|
|
|
|
}
|
2024-04-02 19:47:12 -05:00
|
|
|
}
|
2025-01-07 09:45:14 -08:00
|
|
|
adev = ACPI_COMPANION(dev);
|
|
|
|
if (adev) {
|
|
|
|
/*
|
|
|
|
* Retrieve the resource information in the CRS register, if available.
|
|
|
|
*/
|
|
|
|
status = acpi_walk_resources(adev->handle, METHOD_NAME__CRS,
|
|
|
|
cros_ec_lpc_resources, ec_lpc);
|
2025-01-14 12:21:22 -08:00
|
|
|
if (ACPI_SUCCESS(status) && ec_lpc->mem32.address_length) {
|
2025-01-07 09:45:14 -08:00
|
|
|
ec_lpc->base = devm_ioremap(dev,
|
|
|
|
ec_lpc->mem32.address,
|
|
|
|
ec_lpc->mem32.address_length);
|
|
|
|
if (!ec_lpc->base)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
ec_lpc->read = cros_ec_lpc_direct_read;
|
|
|
|
ec_lpc->write = cros_ec_lpc_direct_write;
|
|
|
|
}
|
2015-02-02 12:26:24 +01:00
|
|
|
}
|
2025-01-07 09:45:14 -08:00
|
|
|
if (!ec_lpc->read) {
|
|
|
|
/*
|
|
|
|
* The Framework Laptop (and possibly other non-ChromeOS devices)
|
|
|
|
* only exposes the eight I/O ports that are required for the Microchip EC.
|
|
|
|
* Requesting a larger reservation will fail.
|
|
|
|
*/
|
|
|
|
if (!devm_request_region(dev, EC_HOST_CMD_REGION0,
|
|
|
|
EC_HOST_CMD_MEC_REGION_SIZE, dev_name(dev))) {
|
|
|
|
dev_err(dev, "couldn't reserve MEC region\n");
|
|
|
|
return -EBUSY;
|
|
|
|
}
|
2015-02-02 12:26:24 +01:00
|
|
|
|
2025-01-07 09:45:14 -08:00
|
|
|
cros_ec_lpc_mec_init(EC_HOST_CMD_REGION0,
|
|
|
|
EC_LPC_ADDR_MEMMAP + EC_MEMMAP_SIZE);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
ec_lpc->read = cros_ec_lpc_mec_read_bytes;
|
|
|
|
ec_lpc->write = cros_ec_lpc_mec_write_bytes;
|
|
|
|
}
|
2025-01-07 09:45:13 -08:00
|
|
|
ret = ec_lpc->read(ec_lpc, EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, buf);
|
2024-06-05 07:33:47 +01:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
2017-05-16 17:46:48 +02:00
|
|
|
if (buf[0] != 'E' || buf[1] != 'C') {
|
2024-04-02 19:47:10 -05:00
|
|
|
if (!devm_request_region(dev, ec_lpc->mmio_memory_base, EC_MEMMAP_SIZE,
|
2022-02-17 10:59:30 -06:00
|
|
|
dev_name(dev))) {
|
|
|
|
dev_err(dev, "couldn't reserve memmap region\n");
|
|
|
|
return -EBUSY;
|
|
|
|
}
|
|
|
|
|
platform/chrome: cros_ec_lpc: Choose Microchip EC at runtime
On many boards, communication between the kernel and the Embedded
Controller happens over an LPC bus. In these cases, the kernel config
CONFIG_CROS_EC_LPC is enabled. Some of these LPC boards contain a
Microchip Embedded Controller (MEC) that is different from the regular
EC. On these devices, the same LPC bus is used, but the protocol is
a little different. In these cases, the CONFIG_CROS_EC_LPC_MEC kernel
config is enabled. Currently, the kernel decides at compile-time whether
or not to use the MEC variant, and, when that kernel option is selected
it breaks the other boards. We would like a kind of runtime detection to
avoid this.
This patch adds that detection mechanism by probing the protocol at
runtime, first we assume that a MEC variant is connected, and if the
protocol fails it fallbacks to the regular EC. This adds a bit of
overload because we try to read twice on those LPC boards that doesn't
contain a MEC variant, but is a better solution than having to select the
EC variant at compile-time.
While here also fix the alignment in Kconfig file for this config option
replacing the spaces by tabs.
Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Reviewed-by: Ezequiel Garcia <ezequiel@collabora.com>
Tested-by: Nick Crews <ncrews@chromium.org>
Reviewed-by: Nick Crews <ncrews@chromium.org>
2019-06-14 23:43:01 +02:00
|
|
|
/* Re-assign read/write operations for the non MEC variant */
|
2025-01-07 09:45:13 -08:00
|
|
|
ec_lpc->read = cros_ec_lpc_read_bytes;
|
|
|
|
ec_lpc->write = cros_ec_lpc_write_bytes;
|
|
|
|
ret = ec_lpc->read(ec_lpc, ec_lpc->mmio_memory_base + EC_MEMMAP_ID, 2,
|
2024-06-05 07:33:47 +01:00
|
|
|
buf);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
platform/chrome: cros_ec_lpc: Choose Microchip EC at runtime
On many boards, communication between the kernel and the Embedded
Controller happens over an LPC bus. In these cases, the kernel config
CONFIG_CROS_EC_LPC is enabled. Some of these LPC boards contain a
Microchip Embedded Controller (MEC) that is different from the regular
EC. On these devices, the same LPC bus is used, but the protocol is
a little different. In these cases, the CONFIG_CROS_EC_LPC_MEC kernel
config is enabled. Currently, the kernel decides at compile-time whether
or not to use the MEC variant, and, when that kernel option is selected
it breaks the other boards. We would like a kind of runtime detection to
avoid this.
This patch adds that detection mechanism by probing the protocol at
runtime, first we assume that a MEC variant is connected, and if the
protocol fails it fallbacks to the regular EC. This adds a bit of
overload because we try to read twice on those LPC boards that doesn't
contain a MEC variant, but is a better solution than having to select the
EC variant at compile-time.
While here also fix the alignment in Kconfig file for this config option
replacing the spaces by tabs.
Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Reviewed-by: Ezequiel Garcia <ezequiel@collabora.com>
Tested-by: Nick Crews <ncrews@chromium.org>
Reviewed-by: Nick Crews <ncrews@chromium.org>
2019-06-14 23:43:01 +02:00
|
|
|
if (buf[0] != 'E' || buf[1] != 'C') {
|
|
|
|
dev_err(dev, "EC ID not detected\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
2015-02-02 12:26:24 +01:00
|
|
|
|
2022-02-17 10:59:30 -06:00
|
|
|
/* Reserve the remaining I/O ports required by the non-MEC protocol. */
|
|
|
|
if (!devm_request_region(dev, EC_HOST_CMD_REGION0 + EC_HOST_CMD_MEC_REGION_SIZE,
|
|
|
|
EC_HOST_CMD_REGION_SIZE - EC_HOST_CMD_MEC_REGION_SIZE,
|
|
|
|
dev_name(dev))) {
|
|
|
|
dev_err(dev, "couldn't reserve remainder of region0\n");
|
|
|
|
return -EBUSY;
|
|
|
|
}
|
|
|
|
if (!devm_request_region(dev, EC_HOST_CMD_REGION1,
|
|
|
|
EC_HOST_CMD_REGION_SIZE, dev_name(dev))) {
|
|
|
|
dev_err(dev, "couldn't reserve region1\n");
|
|
|
|
return -EBUSY;
|
|
|
|
}
|
2015-02-02 12:26:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL);
|
|
|
|
if (!ec_dev)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
platform_set_drvdata(pdev, ec_dev);
|
|
|
|
ec_dev->dev = dev;
|
|
|
|
ec_dev->phys_name = dev_name(dev);
|
|
|
|
ec_dev->cmd_xfer = cros_ec_cmd_xfer_lpc;
|
2015-06-09 13:04:46 +02:00
|
|
|
ec_dev->pkt_xfer = cros_ec_pkt_xfer_lpc;
|
2015-02-02 12:26:24 +01:00
|
|
|
ec_dev->cmd_readmem = cros_ec_lpc_readmem;
|
2015-06-09 13:04:45 +02:00
|
|
|
ec_dev->din_size = sizeof(struct ec_host_response) +
|
|
|
|
sizeof(struct ec_response_get_protocol_info);
|
2024-12-06 09:15:13 +00:00
|
|
|
ec_dev->dout_size = sizeof(struct ec_host_request) + sizeof(struct ec_params_rwsig_action);
|
2024-04-02 19:47:10 -05:00
|
|
|
ec_dev->priv = ec_lpc;
|
2015-02-02 12:26:24 +01:00
|
|
|
|
2018-10-09 10:56:46 -07:00
|
|
|
/*
|
|
|
|
* Some boards do not have an IRQ allotted for cros_ec_lpc,
|
|
|
|
* which makes ENXIO an expected (and safe) scenario.
|
|
|
|
*/
|
2019-11-29 11:22:54 +01:00
|
|
|
irq = platform_get_irq_optional(pdev, 0);
|
2018-10-09 10:56:46 -07:00
|
|
|
if (irq > 0)
|
|
|
|
ec_dev->irq = irq;
|
|
|
|
else if (irq != -ENXIO) {
|
|
|
|
dev_err(dev, "couldn't retrieve IRQ number (%d)\n", irq);
|
|
|
|
return irq;
|
|
|
|
}
|
|
|
|
|
2015-02-02 12:26:24 +01:00
|
|
|
ret = cros_ec_register(ec_dev);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "couldn't register ec_dev (%d)\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-05-16 17:46:48 +02:00
|
|
|
/*
|
|
|
|
* Connect a notify handler to process MKBP messages if we have a
|
|
|
|
* companion ACPI device.
|
|
|
|
*/
|
|
|
|
if (adev) {
|
|
|
|
status = acpi_install_notify_handler(adev->handle,
|
|
|
|
ACPI_ALL_NOTIFY,
|
|
|
|
cros_ec_lpc_acpi_notify,
|
|
|
|
ec_dev);
|
|
|
|
if (ACPI_FAILURE(status))
|
|
|
|
dev_warn(dev, "Failed to register notifier %08x\n",
|
|
|
|
status);
|
|
|
|
}
|
|
|
|
|
2015-02-02 12:26:24 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-09-27 10:10:17 +02:00
|
|
|
static void cros_ec_lpc_remove(struct platform_device *pdev)
|
2015-02-02 12:26:24 +01:00
|
|
|
{
|
2019-09-02 11:53:00 +02:00
|
|
|
struct cros_ec_device *ec_dev = platform_get_drvdata(pdev);
|
2017-05-16 17:46:48 +02:00
|
|
|
struct acpi_device *adev;
|
|
|
|
|
|
|
|
adev = ACPI_COMPANION(&pdev->dev);
|
|
|
|
if (adev)
|
|
|
|
acpi_remove_notify_handler(adev->handle, ACPI_ALL_NOTIFY,
|
|
|
|
cros_ec_lpc_acpi_notify);
|
2015-02-02 12:26:24 +01:00
|
|
|
|
2022-01-23 18:52:00 +01:00
|
|
|
cros_ec_unregister(ec_dev);
|
2015-02-02 12:26:24 +01:00
|
|
|
}
|
|
|
|
|
2024-08-06 21:54:09 -05:00
|
|
|
static const struct lpc_driver_data framework_laptop_npcx_lpc_driver_data __initconst = {
|
2024-04-02 19:47:13 -05:00
|
|
|
.quirks = CROS_EC_LPC_QUIRK_REMAP_MEMORY,
|
|
|
|
.quirk_mmio_memory_base = 0xE00,
|
|
|
|
};
|
|
|
|
|
2024-08-06 21:54:09 -05:00
|
|
|
static const struct lpc_driver_data framework_laptop_mec_lpc_driver_data __initconst = {
|
2024-06-05 07:33:51 +01:00
|
|
|
.quirks = CROS_EC_LPC_QUIRK_ACPI_ID|CROS_EC_LPC_QUIRK_AML_MUTEX,
|
|
|
|
.quirk_acpi_id = "PNP0C09",
|
|
|
|
.quirk_aml_mutex_name = "ECMT",
|
|
|
|
};
|
|
|
|
|
2025-01-29 02:13:29 +08:00
|
|
|
static const struct acpi_device_id cros_ec_lpc_acpi_device_ids[] = {
|
|
|
|
{ ACPI_DRV_NAME, 0 },
|
|
|
|
{ FRMW_ACPI_DRV_NAME, (kernel_ulong_t)&framework_laptop_npcx_lpc_driver_data },
|
|
|
|
{ }
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(acpi, cros_ec_lpc_acpi_device_ids);
|
|
|
|
|
2017-09-14 11:59:30 +02:00
|
|
|
static const struct dmi_system_id cros_ec_lpc_dmi_table[] __initconst = {
|
2015-02-02 12:26:24 +01:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Today all Chromebooks/boxes ship with Google_* as version and
|
|
|
|
* coreboot as bios vendor. No other systems with this
|
|
|
|
* combination are known to date.
|
|
|
|
*/
|
|
|
|
.matches = {
|
|
|
|
DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"),
|
|
|
|
DMI_MATCH(DMI_BIOS_VERSION, "Google_"),
|
|
|
|
},
|
|
|
|
},
|
2018-03-07 14:56:43 +01:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* If the box is running custom coreboot firmware then the
|
|
|
|
* DMI BIOS version string will not be matched by "Google_",
|
|
|
|
* but the system vendor string will still be matched by
|
|
|
|
* "GOOGLE".
|
|
|
|
*/
|
|
|
|
.matches = {
|
|
|
|
DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"),
|
|
|
|
DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"),
|
|
|
|
},
|
|
|
|
},
|
2015-02-02 12:26:24 +01:00
|
|
|
{
|
|
|
|
/* x86-link, the Chromebook Pixel. */
|
|
|
|
.matches = {
|
|
|
|
DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"),
|
|
|
|
DMI_MATCH(DMI_PRODUCT_NAME, "Link"),
|
|
|
|
},
|
|
|
|
},
|
2015-06-22 08:27:19 +02:00
|
|
|
{
|
|
|
|
/* x86-samus, the Chromebook Pixel 2. */
|
|
|
|
.matches = {
|
|
|
|
DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"),
|
|
|
|
DMI_MATCH(DMI_PRODUCT_NAME, "Samus"),
|
|
|
|
},
|
|
|
|
},
|
2015-02-02 12:26:24 +01:00
|
|
|
{
|
|
|
|
/* x86-peppy, the Acer C720 Chromebook. */
|
|
|
|
.matches = {
|
|
|
|
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
|
|
|
DMI_MATCH(DMI_PRODUCT_NAME, "Peppy"),
|
|
|
|
},
|
|
|
|
},
|
2017-12-01 14:42:22 +01:00
|
|
|
{
|
|
|
|
/* x86-glimmer, the Lenovo Thinkpad Yoga 11e. */
|
|
|
|
.matches = {
|
|
|
|
DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"),
|
|
|
|
DMI_MATCH(DMI_PRODUCT_NAME, "Glimmer"),
|
|
|
|
},
|
|
|
|
},
|
2022-02-17 10:59:29 -06:00
|
|
|
/* A small number of non-Chromebook/box machines also use the ChromeOS EC */
|
|
|
|
{
|
2024-08-06 21:54:09 -05:00
|
|
|
/* Framework Laptop (11th Gen Intel Core) */
|
2024-04-02 19:47:13 -05:00
|
|
|
.matches = {
|
|
|
|
DMI_MATCH(DMI_SYS_VENDOR, "Framework"),
|
2024-08-06 21:54:09 -05:00
|
|
|
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Laptop"),
|
|
|
|
},
|
|
|
|
.driver_data = (void *)&framework_laptop_mec_lpc_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
/* Framework Laptop (12th Gen Intel Core) */
|
|
|
|
.matches = {
|
|
|
|
DMI_MATCH(DMI_SYS_VENDOR, "Framework"),
|
2024-12-24 12:55:58 -06:00
|
|
|
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Laptop (12th Gen Intel Core)"),
|
2024-08-06 21:54:09 -05:00
|
|
|
},
|
|
|
|
.driver_data = (void *)&framework_laptop_mec_lpc_driver_data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
/* Framework Laptop (13th Gen Intel Core) */
|
|
|
|
.matches = {
|
|
|
|
DMI_MATCH(DMI_SYS_VENDOR, "Framework"),
|
2024-12-24 12:55:58 -06:00
|
|
|
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Laptop (13th Gen Intel Core)"),
|
2024-04-02 19:47:13 -05:00
|
|
|
},
|
2024-08-06 21:54:09 -05:00
|
|
|
.driver_data = (void *)&framework_laptop_mec_lpc_driver_data,
|
2024-04-02 19:47:13 -05:00
|
|
|
},
|
|
|
|
{
|
2024-08-06 21:54:09 -05:00
|
|
|
/*
|
|
|
|
* All remaining Framework Laptop models (13 AMD Ryzen, 16 AMD
|
|
|
|
* Ryzen, Intel Core Ultra)
|
|
|
|
*/
|
2022-02-17 10:59:29 -06:00
|
|
|
.matches = {
|
|
|
|
DMI_MATCH(DMI_SYS_VENDOR, "Framework"),
|
2024-08-06 21:54:09 -05:00
|
|
|
DMI_MATCH(DMI_PRODUCT_FAMILY, "Laptop"),
|
2022-02-17 10:59:29 -06:00
|
|
|
},
|
2024-08-06 21:54:09 -05:00
|
|
|
.driver_data = (void *)&framework_laptop_npcx_lpc_driver_data,
|
2022-02-17 10:59:29 -06:00
|
|
|
},
|
2015-02-02 12:26:24 +01:00
|
|
|
{ /* sentinel */ }
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(dmi, cros_ec_lpc_dmi_table);
|
|
|
|
|
2017-05-16 17:46:48 +02:00
|
|
|
#ifdef CONFIG_PM_SLEEP
|
2023-05-15 14:25:52 -06:00
|
|
|
static int cros_ec_lpc_prepare(struct device *dev)
|
2017-05-16 17:46:48 +02:00
|
|
|
{
|
|
|
|
struct cros_ec_device *ec_dev = dev_get_drvdata(dev);
|
2023-10-27 16:02:22 -05:00
|
|
|
return cros_ec_suspend_prepare(ec_dev);
|
2017-05-16 17:46:48 +02:00
|
|
|
}
|
|
|
|
|
2023-05-15 14:25:52 -06:00
|
|
|
static void cros_ec_lpc_complete(struct device *dev)
|
2017-05-16 17:46:48 +02:00
|
|
|
{
|
|
|
|
struct cros_ec_device *ec_dev = dev_get_drvdata(dev);
|
2023-10-27 16:02:22 -05:00
|
|
|
cros_ec_resume_complete(ec_dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cros_ec_lpc_suspend_late(struct device *dev)
|
|
|
|
{
|
|
|
|
struct cros_ec_device *ec_dev = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
return cros_ec_suspend_late(ec_dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cros_ec_lpc_resume_early(struct device *dev)
|
|
|
|
{
|
|
|
|
struct cros_ec_device *ec_dev = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
return cros_ec_resume_early(ec_dev);
|
2017-05-16 17:46:48 +02:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-05-30 16:49:32 +08:00
|
|
|
static const struct dev_pm_ops cros_ec_lpc_pm_ops = {
|
2023-05-15 14:25:52 -06:00
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
|
|
.prepare = cros_ec_lpc_prepare,
|
2023-10-27 16:02:22 -05:00
|
|
|
.complete = cros_ec_lpc_complete,
|
2023-05-15 14:25:52 -06:00
|
|
|
#endif
|
2023-10-27 16:02:22 -05:00
|
|
|
SET_LATE_SYSTEM_SLEEP_PM_OPS(cros_ec_lpc_suspend_late, cros_ec_lpc_resume_early)
|
2017-05-16 17:46:48 +02:00
|
|
|
};
|
|
|
|
|
2015-02-02 12:26:24 +01:00
|
|
|
static struct platform_driver cros_ec_lpc_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = DRV_NAME,
|
2017-05-16 17:46:48 +02:00
|
|
|
.acpi_match_table = cros_ec_lpc_acpi_device_ids,
|
2017-05-16 17:46:48 +02:00
|
|
|
.pm = &cros_ec_lpc_pm_ops,
|
platform/chrome: cros_ec_lpc: Force synchronous probe
This reverts commit bd88b965ae8c ("platform/chrome: cros_ec_lpc: Mark
PROBE_PREFER_ASYNCHRONOUS"), and then some.
It has been reported that there are issues with 'cros-ec-keyb' devices
that are children of this. As noted in the initial patch for its ACPI
support (commit ba0f32141bc5 ("Input: cros_ec_keyb - handle x86
detachable/convertible Chromebooks")), it's possible to probe an ACPI
child device before its parent is probed -- hence the need for
EPROBE_DEFER. Unfortunately, poking your parent's dev_get_drvdata()
isn't safe with asynchronous probe, as there's no locking, and the
ordering is all wrong anyway (drvdata is set before the device is
*really* ready).
Because this parent/child relationship has known issues, let's go the
other direction and force synchronous probe, until we resolve the
issues.
Possible solutions involve adding device links, so we ensure the child
doesn't probe before the parent is done; or perhaps some other larger
refactoring (auxiliary bus?). But that might take a little more effort
and review, as there are many other potential sub-devices of
cros_ec_lpc that could need patching.
Note that we don't have the same problem for non-ACPI cros-ec hosts,
like cros-ec-spi (commit 015e4b05c377 ("platform/chrome: cros_ec_spi:
Set PROBE_PREFER_ASYNCHRONOUS")), because its sub-devices aren't created
until cros_ec_register(), or they don't exist at all (e.g., FPMCU uses).
Fixes: bd88b965ae8c ("platform/chrome: cros_ec_lpc: Mark PROBE_PREFER_ASYNCHRONOUS")
Signed-off-by: Brian Norris <briannorris@chromium.org>
Reviewed-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Reviewed-by: Guenter Roeck <groeck@chromium.org>
Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
Link: https://lore.kernel.org/r/20221111231302.3458191-1-briannorris@chromium.org
2022-11-11 15:13:01 -08:00
|
|
|
/*
|
|
|
|
* ACPI child devices may probe before us, and they racily
|
|
|
|
* check our drvdata pointer. Force synchronous probe until
|
|
|
|
* those races are resolved.
|
|
|
|
*/
|
|
|
|
.probe_type = PROBE_FORCE_SYNCHRONOUS,
|
2015-02-02 12:26:24 +01:00
|
|
|
},
|
|
|
|
.probe = cros_ec_lpc_probe,
|
2024-10-28 09:26:11 +01:00
|
|
|
.remove = cros_ec_lpc_remove,
|
2015-02-02 12:26:24 +01:00
|
|
|
};
|
|
|
|
|
2017-12-01 14:42:21 +01:00
|
|
|
static struct platform_device cros_ec_lpc_device = {
|
|
|
|
.name = DRV_NAME
|
|
|
|
};
|
|
|
|
|
2015-02-02 12:26:24 +01:00
|
|
|
static int __init cros_ec_lpc_init(void)
|
|
|
|
{
|
|
|
|
int ret;
|
2024-04-02 19:47:11 -05:00
|
|
|
const struct dmi_system_id *dmi_match;
|
2015-02-02 12:26:24 +01:00
|
|
|
|
2025-01-29 02:13:29 +08:00
|
|
|
cros_ec_lpc_acpi_device_found = !!cros_ec_lpc_get_device(ACPI_DRV_NAME) ||
|
|
|
|
!!cros_ec_lpc_get_device(FRMW_ACPI_DRV_NAME);
|
2018-05-22 16:08:41 -07:00
|
|
|
|
2024-04-02 19:47:11 -05:00
|
|
|
dmi_match = dmi_first_match(cros_ec_lpc_dmi_table);
|
|
|
|
|
|
|
|
if (!cros_ec_lpc_acpi_device_found && !dmi_match) {
|
2015-02-02 12:26:24 +01:00
|
|
|
pr_err(DRV_NAME ": unsupported system.\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Register the driver */
|
|
|
|
ret = platform_driver_register(&cros_ec_lpc_driver);
|
|
|
|
if (ret) {
|
|
|
|
pr_err(DRV_NAME ": can't register driver: %d\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-12-01 14:42:21 +01:00
|
|
|
if (!cros_ec_lpc_acpi_device_found) {
|
2024-04-02 19:47:11 -05:00
|
|
|
/* Pass the DMI match's driver data down to the platform device */
|
|
|
|
platform_set_drvdata(&cros_ec_lpc_device, dmi_match->driver_data);
|
|
|
|
|
2017-12-01 14:42:21 +01:00
|
|
|
/* Register the device, and it'll get hooked up automatically */
|
|
|
|
ret = platform_device_register(&cros_ec_lpc_device);
|
|
|
|
if (ret) {
|
|
|
|
pr_err(DRV_NAME ": can't register device: %d\n", ret);
|
|
|
|
platform_driver_unregister(&cros_ec_lpc_driver);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2015-02-02 12:26:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit cros_ec_lpc_exit(void)
|
|
|
|
{
|
2017-12-01 14:42:21 +01:00
|
|
|
if (!cros_ec_lpc_acpi_device_found)
|
|
|
|
platform_device_unregister(&cros_ec_lpc_device);
|
2015-02-02 12:26:24 +01:00
|
|
|
platform_driver_unregister(&cros_ec_lpc_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
module_init(cros_ec_lpc_init);
|
|
|
|
module_exit(cros_ec_lpc_exit);
|
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_DESCRIPTION("ChromeOS EC LPC driver");
|