2019-02-08 19:45:10

by Nick Crews

[permalink] [raw]
Subject: [PATCH v7 0/4] platform/chrome: Add basic support for Wilco EC


There is a new chromebook that contains a different Embedded Controller
(codename Wilco) than the rest of the chromebook series. Thus the kernel
requires a different driver than the already existing and generalized
cros_ec_* drivers. The core of the communication with the EC
is implemented in wilco_ec/mailbox.c, using a simple byte-level protocol
with a checksum, transmitted over an eSPI bus.

In this patch series I only introduce a very barebones
driver with a debugfs interface for sending/receiving raw byte
sequences to the EC, as well as a standard RTC driver with read/write
functionality. Later, I will hopefully include more specialized
drivers such as a keyboard backlight driver, an EC event notification node,
telemetry data query node, etc.

The entry point of the driver is wilco_ec/core.c,
which is responsible for several tasks:
- Initialize the register interface that is used by wilco_ec_mailbox()
- Create a platform device which is picked up by the debugfs driver
- Create a platform device which is picked up by the RTC driver

A full thread of the development of these patches can be found at
https://chromium-review.googlesource.com/c/1356082. This thread contains
comments and revisions that could be helpful in understanding how the
driver arrived at the state it is in now. The thread also contains some
ChromeOS specific patches that actually enable the driver. If you want
to test the patch yourself, you would have to install the ChromeOS SDK
and cherry pick in these patches, and even then you would need a Sarien
device to actually run it.

Thank you for your comments!

Changes in v7:
- Switch to #define for MAX_WORD_SIZE so array size
can be determined at compile time.

Changes in v6:
- Re-added WILCO_EC_FLAG_EXTENDED_DATA and went back to always
reading either EC_MAILBOX_DATA_SIZE or EC_MAILBOX_DATA_SIZE_EXTENDED
bytes, since it turns out the EC always expects you to calculate the
checksum over all of them.
- Split WILCO_EC_MSG_TELEMETRY into WILCO_EC_MSG_TELEMETRY_SHORT and
WILCO_EC_MSG_TELEMETRY_LONG, since there will be two different
commands.
- A couple comment and err message polishes
- s/4.19/5.1/ for kernel version in documentation, since that is
the version this patch should land in.
- Instead of requiring at least 3 bytes for msg type and command,
now just require two for msg type. We can skip the command.
- Fixed error checking in probe() so that errors are hidden, without
causing more errors or unextpected behavior.
- Some comment polishing.
- In the core, actually unregister the debugfs child platform_device
- In the core, actually unregister the RTC child platform_device.

Changes in v5:
- move checking of NO_RESPONSE flag before timeout check,
so now timeout doesn't always happen when EC isn't supposed
to respond.
- rm WILCO_EC_FLAG_EXTENDED_DATA, that is already obvious from
wilco_ec_message.response_size
- core now always continues regardless of debugfs failure
- mv documentation to file header
- Check for OOM
- rm unneeded check if debug_info is allocqated
- rm bogus comment
- add space around "+"
- rm WILCO_EC_FLAG_EXTENDED_DATA, that is already obvious from
wilco_ec_message.response_size

Changes in v4:
- add #define DRV_NAME
- remove redundant Kconfig nesting
- changed my email to @chromium.org
- Add better explanation of what core.c does
- Change debugfs driver to be a separate module
- Change me email to @chromium.org from @google.com
- Change CONFIG_WILCO_EC_SYSFS_RAW to
CONFIG_WILCO_EC_DEBUGFS
- Change me email to @chromium.org from @google.com
- Move "Add RTC driver" before "Add sysfs attributes" so that
it could get accepted earlier, since it is less contentious

Changes in v3:
- Change <= to >= in mec_in_range()
- Add " - EC_HOST_CMD_REGION0" to offset arg for io_bytes_mec()
- remove unused ret in probe()
- Add newline spacer in probe()
- rm unnecessary res in get_resource()
- s/8bit/8-bit
- rm first sleep when sending command to EC
- Move the attribute to the debugfs system
- Move the implementation to debugfs.c
- Improve the raw hex parsing
- Encapsulate global variables in one object
- Add safety check when passing less than 3 bytes
- Move documentation to debugfs-wilco-ec
- rm #define for driver name
- Don't compute weekday when reading from RTC.
Still set weekday when writing, as RTC needs this
to control advanced power scheduling
- rm check for invalid month data
- Set range_min and range_max on rtc_device

Changes in v2:
- Fixed kernel-doc comments
- Fixed include of linux/mfd/cros_ec_lpc_mec.h
- cros_ec_lpc_mec_in_range() returns -EINVAL on error
- Added parens around macro variables
- Remove COMPILE_TEST from Kconfig because inb()/outb()
won't work on anything but X86
- Add myself as module author
- Tweak mailbox()
- Add sysfs documentation
- rm duplicate EC_MAILBOX_DATA_SIZE defs
- Make docstrings follow kernel style
- Fix tags in commit msg
- Move Kconfig to subdirectory
- Reading raw now includes ASCII translation
- rm license boiler plate
- rm "wilco_ec_rtc -" prefix in docstring
- Make rtc driver its own module within the drivers/rtc/ directory
- Register a rtc device from core.c that is picked up by this driver

Nick Crews (4):
cros_ec: Remove cros_ec dependency in lpc_mec
platform/chrome: Add new driver for Wilco EC
platform/chrome: Add support for raw commands in debugfs
platform/chrome: rtc: Add RTC driver

Documentation/ABI/testing/debugfs-wilco-ec | 23 ++
drivers/platform/chrome/Kconfig | 4 +-
drivers/platform/chrome/Makefile | 2 +
drivers/platform/chrome/cros_ec_lpc_mec.c | 52 ++++-
drivers/platform/chrome/cros_ec_lpc_mec.h | 43 ++--
drivers/platform/chrome/cros_ec_lpc_reg.c | 47 ++--
drivers/platform/chrome/wilco_ec/Kconfig | 21 ++
drivers/platform/chrome/wilco_ec/Makefile | 6 +
drivers/platform/chrome/wilco_ec/core.c | 136 ++++++++++++
drivers/platform/chrome/wilco_ec/debugfs.c | 238 +++++++++++++++++++++
drivers/platform/chrome/wilco_ec/mailbox.c | 236 ++++++++++++++++++++
drivers/rtc/Kconfig | 11 +
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-wilco-ec.c | 177 +++++++++++++++
include/linux/platform_data/wilco-ec.h | 144 +++++++++++++
15 files changed, 1081 insertions(+), 60 deletions(-)
create mode 100644 Documentation/ABI/testing/debugfs-wilco-ec
create mode 100644 drivers/platform/chrome/wilco_ec/Kconfig
create mode 100644 drivers/platform/chrome/wilco_ec/Makefile
create mode 100644 drivers/platform/chrome/wilco_ec/core.c
create mode 100644 drivers/platform/chrome/wilco_ec/debugfs.c
create mode 100644 drivers/platform/chrome/wilco_ec/mailbox.c
create mode 100644 drivers/rtc/rtc-wilco-ec.c
create mode 100644 include/linux/platform_data/wilco-ec.h

--
2.20.1.791.gb4d0f1c61a-goog



2019-02-08 19:44:50

by Nick Crews

[permalink] [raw]
Subject: [PATCH v7 3/4] platform/chrome: Add support for raw commands in debugfs

Add a debugfs attribute that allows sending raw commands to the EC.
This is useful for development and debug but should not be enabled
in a production environment.

To test:
Get the EC firmware build date
First send the request command
> echo 00 f0 38 00 03 00 > raw
Then read the result. "12/21/18" is in the middle of the response
> cat raw
00 31 32 2f 32 31 2f 31 38 00 00 0f 01 00 01 00 .12/21/18.......

Get the EC firmware build date
First send the request command
> echo 00 f0 38 00 03 00 > raw
Then read the result. "12/21/18" is in the middle of the response
> cat raw
00 31 32 2f 32 31 2f 31 38 00 00 0f 01 00 01 00 .12/21/18.......

Signed-off-by: Duncan Laurie <[email protected]>
Signed-off-by: Nick Crews <[email protected]>
---

Changes in v7:
- Switch to #define for MAX_WORD_SIZE so array size
can be determined at compile time.

Changes in v6:
- s/4.19/5.1/ for kernel version in documentation, since that is
the version this patch should land in.
- Instead of requiring at least 3 bytes for msg type and command,
now just require two for msg type. We can skip the command.
- Fixed error checking in probe() so that errors are hidden, without
causing more errors or unextpected behavior.
- Some comment polishing.
- In the core, actually unregister the debugfs child platform_device

Changes in v5:
- core now always continues regardless of debugfs failure
- mv documentation to file header
- Check for OOM
- rm unneeded check if debug_info is allocqated
- rm bogus comment
- add space around "+"
- rm WILCO_EC_FLAG_EXTENDED_DATA, that is already obvious from
wilco_ec_message.response_size

Changes in v4:
- Change debugfs driver to be a separate module
- Change me email to @chromium.org from @google.com
- Change CONFIG_WILCO_EC_SYSFS_RAW to
CONFIG_WILCO_EC_DEBUGFS

Changes in v3:
- Move the attribute to the debugfs system
- Move the implementation to debugfs.c
- Improve the raw hex parsing
- Encapsulate global variables in one object
- Add safety check when passing less than 3 bytes
- Move documentation to debugfs-wilco-ec

Changes in v2:
- Add sysfs documentation
- rm duplicate EC_MAILBOX_DATA_SIZE defs
- Make docstrings follow kernel style
- Fix tags in commit msg
- Move Kconfig to subdirectory
- Reading raw now includes ASCII translation

Documentation/ABI/testing/debugfs-wilco-ec | 23 ++
drivers/platform/chrome/wilco_ec/Kconfig | 10 +
drivers/platform/chrome/wilco_ec/Makefile | 2 +
drivers/platform/chrome/wilco_ec/core.c | 14 ++
drivers/platform/chrome/wilco_ec/debugfs.c | 238 +++++++++++++++++++++
include/linux/platform_data/wilco-ec.h | 2 +
6 files changed, 289 insertions(+)
create mode 100644 Documentation/ABI/testing/debugfs-wilco-ec
create mode 100644 drivers/platform/chrome/wilco_ec/debugfs.c

diff --git a/Documentation/ABI/testing/debugfs-wilco-ec b/Documentation/ABI/testing/debugfs-wilco-ec
new file mode 100644
index 000000000000..f814f112e213
--- /dev/null
+++ b/Documentation/ABI/testing/debugfs-wilco-ec
@@ -0,0 +1,23 @@
+What: /sys/kernel/debug/wilco_ec/raw
+Date: January 2019
+KernelVersion: 5.1
+Description:
+ Write and read raw mailbox commands to the EC.
+
+ For writing:
+ Bytes 0-1 indicate the message type:
+ 00 F0 = Execute Legacy Command
+ 00 F2 = Read/Write NVRAM Property
+ Byte 2 provides the command code
+ Bytes 3+ consist of the data passed in the request
+
+ At least three bytes are required, for the msg type and command,
+ with additional bytes optional for additional data.
+
+ Example:
+ // Request EC info type 3 (EC firmware build date)
+ $ echo 00 f0 38 00 03 00 > raw
+ // View the result. The decoded ASCII result "12/21/18" is
+ // included after the raw hex.
+ $ cat raw
+ 00 31 32 2f 32 31 2f 31 38 00 38 00 01 00 2f 00 .12/21/18.8...
diff --git a/drivers/platform/chrome/wilco_ec/Kconfig b/drivers/platform/chrome/wilco_ec/Kconfig
index 20945a301ec6..fb6d9ff33e41 100644
--- a/drivers/platform/chrome/wilco_ec/Kconfig
+++ b/drivers/platform/chrome/wilco_ec/Kconfig
@@ -9,3 +9,13 @@ config WILCO_EC

To compile this driver as a module, choose M here: the
module will be called wilco_ec.
+
+config WILCO_EC_DEBUGFS
+ tristate "Enable raw access to EC via debugfs"
+ depends on WILCO_EC
+ help
+ If you say Y here, you get support for sending raw commands to
+ the Wilco EC via debugfs. These commands do not do any byte
+ manipulation and allow for testing arbitrary commands. This
+ interface is intended for debug only and will not be present
+ on production devices.
diff --git a/drivers/platform/chrome/wilco_ec/Makefile b/drivers/platform/chrome/wilco_ec/Makefile
index 03b32301dc61..063e7fb4ea17 100644
--- a/drivers/platform/chrome/wilco_ec/Makefile
+++ b/drivers/platform/chrome/wilco_ec/Makefile
@@ -2,3 +2,5 @@

wilco_ec-objs := core.o mailbox.o
obj-$(CONFIG_WILCO_EC) += wilco_ec.o
+wilco_ec_debugfs-objs := debugfs.o
+obj-$(CONFIG_WILCO_EC_DEBUGFS) += wilco_ec_debugfs.o
diff --git a/drivers/platform/chrome/wilco_ec/core.c b/drivers/platform/chrome/wilco_ec/core.c
index 20ecc580d108..ae86cae216fd 100644
--- a/drivers/platform/chrome/wilco_ec/core.c
+++ b/drivers/platform/chrome/wilco_ec/core.c
@@ -69,11 +69,25 @@ static int wilco_ec_probe(struct platform_device *pdev)
cros_ec_lpc_mec_init(ec->io_packet->start,
ec->io_packet->start + EC_MAILBOX_DATA_SIZE);

+ /*
+ * Register a debugfs platform device that will get picked up by the
+ * debugfs driver. Ignore failure.
+ */
+ ec->debugfs_pdev = platform_device_register_data(dev,
+ "wilco-ec-debugfs",
+ PLATFORM_DEVID_AUTO,
+ NULL, 0);
+
return 0;
}

static int wilco_ec_remove(struct platform_device *pdev)
{
+ struct wilco_ec_device *ec = platform_get_drvdata(pdev);
+
+ if (ec->debugfs_pdev)
+ platform_device_unregister(ec->debugfs_pdev);
+
/* Teardown cros_ec interface */
cros_ec_lpc_mec_destroy();

diff --git a/drivers/platform/chrome/wilco_ec/debugfs.c b/drivers/platform/chrome/wilco_ec/debugfs.c
new file mode 100644
index 000000000000..46ff3b6c46c7
--- /dev/null
+++ b/drivers/platform/chrome/wilco_ec/debugfs.c
@@ -0,0 +1,238 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * debugfs attributes for Wilco EC
+ *
+ * Copyright 2019 Google LLC
+ *
+ * There is only one attribute used for debugging, called raw.
+ * You can write a hexadecimal sentence to raw, and that series of bytes
+ * will be sent to the EC. Then, you can read the bytes of response
+ * by reading from raw.
+ *
+ * For writing:
+ * Bytes 0-1 indicate the message type:
+ * 00 F0 = Execute Legacy Command
+ * 00 F2 = Read/Write NVRAM Property
+ * Byte 2 provides the command code
+ * Bytes 3+ consist of the data passed in the request
+ *
+ * When referencing the EC interface spec, byte 2 corresponds to MBOX[0],
+ * byte 3 corresponds to MBOX[1], etc.
+ *
+ * At least three bytes are required, for the msg type and command,
+ * with additional bytes optional for additional data.
+ *
+ * Example:
+ * // Request EC info type 3 (EC firmware build date)
+ * $ echo 00 f0 38 00 03 00 > raw
+ * // View the result. The decoded ASCII result "12/21/18" is
+ * // included after the raw hex.
+ * $ cat raw
+ * 00 31 32 2f 32 31 2f 31 38 00 38 00 01 00 2f 00 .12/21/18.8...
+ */
+
+#include <linux/ctype.h>
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <linux/module.h>
+#include <linux/platform_data/wilco-ec.h>
+#include <linux/platform_device.h>
+
+#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)
+
+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 formatted_data[FORMATTED_BUFFER_SIZE];
+};
+static struct wilco_ec_debugfs *debug_info;
+
+/**
+ * parse_hex_sentence() - Convert a ascii hex representation into byte array.
+ * @in: Input buffer of ascii.
+ * @isize: Length of input buffer.
+ * @out: Output buffer.
+ * @osize: Length of output buffer, e.g. max number of bytes to parse.
+ *
+ * An valid input is a series of ascii hexadecimal numbers, separated by spaces.
+ * An example valid input is
+ * " 00 f2 0 000076 6 0 ff"
+ *
+ * If an individual "word" within the hex sentence is longer than MAX_WORD_SIZE,
+ * then the sentence is illegal, and parsing will fail.
+ *
+ * Return: Number of bytes parsed, or negative error code on failure.
+ */
+static int parse_hex_sentence(const char *in, int isize, u8 *out, int osize)
+{
+ int n_parsed = 0;
+ int word_start = 0;
+ int word_end;
+ int word_len;
+ /* Temp buffer for holding a "word" of chars that represents one byte */
+ #define MAX_WORD_SIZE 16
+ char tmp[MAX_WORD_SIZE + 1];
+ u8 byte;
+
+ while (word_start < isize && n_parsed < osize) {
+ /* Find the start of the next word */
+ while (word_start < isize && isspace(in[word_start]))
+ word_start++;
+ /* reached the end of the input before next word? */
+ if (word_start >= isize)
+ break;
+
+ /* Find the end of this word */
+ word_end = word_start;
+ while (word_end < isize && !isspace(in[word_end]))
+ word_end++;
+
+ /* Copy to a tmp NULL terminated string */
+ word_len = word_end - word_start;
+ if (word_len > MAX_WORD_SIZE)
+ return -EINVAL;
+ memcpy(tmp, in + word_start, word_len);
+ tmp[word_len] = '\0';
+
+ /*
+ * Convert from hex string, place in output. If fails to parse,
+ * just return -EINVAL because specific error code is only
+ * relevant for this one word, returning it would be confusing.
+ */
+ if (kstrtou8(tmp, 16, &byte))
+ return -EINVAL;
+ out[n_parsed++] = byte;
+
+ word_start = word_end;
+ }
+ return n_parsed;
+}
+
+/* The message type takes up two bytes*/
+#define TYPE_AND_DATA_SIZE ((EC_MAILBOX_DATA_SIZE) + 2)
+
+static ssize_t raw_write(struct file *file, const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ char *buf = debug_info->formatted_data;
+ struct wilco_ec_message msg;
+ u8 request_data[TYPE_AND_DATA_SIZE];
+ ssize_t kcount;
+ int ret;
+
+ if (count > FORMATTED_BUFFER_SIZE)
+ return -EINVAL;
+
+ kcount = simple_write_to_buffer(buf, FORMATTED_BUFFER_SIZE, ppos,
+ user_buf, count);
+ if (kcount < 0)
+ return kcount;
+
+ ret = parse_hex_sentence(buf, kcount, request_data, TYPE_AND_DATA_SIZE);
+ if (ret < 0)
+ return ret;
+ /* Need at least two bytes for message type */
+ if (ret < 2)
+ return -EINVAL;
+
+ /* Clear response data buffer */
+ memset(debug_info->raw_data, '\0', EC_MAILBOX_DATA_SIZE_EXTENDED);
+
+ msg.type = request_data[0] << 8 | request_data[1];
+ msg.flags = WILCO_EC_FLAG_RAW;
+ msg.command = ret > 2 ? request_data[2] : 0;
+ msg.request_data = ret > 3 ? request_data + 3 : 0;
+ msg.request_size = ret - 3;
+ 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;
+ debug_info->response_size = ret;
+
+ return count;
+}
+
+static ssize_t raw_read(struct file *file, char __user *user_buf, size_t count,
+ loff_t *ppos)
+{
+ int fmt_len = 0;
+
+ if (debug_info->response_size) {
+ fmt_len = hex_dump_to_buffer(debug_info->raw_data,
+ debug_info->response_size,
+ 16, 1, debug_info->formatted_data,
+ FORMATTED_BUFFER_SIZE, true);
+ /* Only return response the first time it is read */
+ debug_info->response_size = 0;
+ }
+
+ return simple_read_from_buffer(user_buf, count, ppos,
+ debug_info->formatted_data, fmt_len);
+}
+
+static const struct file_operations fops_raw = {
+ .owner = THIS_MODULE,
+ .read = raw_read,
+ .write = raw_write,
+ .llseek = no_llseek,
+};
+
+/**
+ * wilco_ec_debugfs_probe() - Create the debugfs node
+ * @pdev: The platform device, probably created in core.c
+ *
+ * Try to create a debugfs node. If it fails, then we don't want to change
+ * behavior at all, this is for debugging after all. Just fail silently.
+ *
+ * Return: 0 always.
+ */
+static int wilco_ec_debugfs_probe(struct platform_device *pdev)
+{
+ struct wilco_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
+
+ debug_info = devm_kzalloc(&pdev->dev, sizeof(*debug_info), GFP_KERNEL);
+ if (!debug_info)
+ return 0;
+ debug_info->ec = ec;
+ debug_info->dir = debugfs_create_dir("wilco_ec", NULL);
+ if (!debug_info->dir)
+ return 0;
+ debugfs_create_file("raw", 0644, debug_info->dir, NULL, &fops_raw);
+
+ return 0;
+}
+
+static int wilco_ec_debugfs_remove(struct platform_device *pdev)
+{
+ debugfs_remove_recursive(debug_info->dir);
+
+ return 0;
+}
+
+static struct platform_driver wilco_ec_debugfs_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ },
+ .probe = wilco_ec_debugfs_probe,
+ .remove = wilco_ec_debugfs_remove,
+};
+
+module_platform_driver(wilco_ec_debugfs_driver);
+
+MODULE_ALIAS("platform:" DRV_NAME);
+MODULE_AUTHOR("Nick Crews <[email protected]>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Wilco EC debugfs driver");
diff --git a/include/linux/platform_data/wilco-ec.h b/include/linux/platform_data/wilco-ec.h
index 0feb4b520a54..5344975afa1a 100644
--- a/include/linux/platform_data/wilco-ec.h
+++ b/include/linux/platform_data/wilco-ec.h
@@ -34,6 +34,7 @@
* @data_buffer: Buffer used for EC communication. The same buffer
* is used to hold the request and the response.
* @data_size: Size of the data buffer used for EC communication.
+ * @debugfs_pdev: The child platform_device used by the debugfs sub-driver.
*/
struct wilco_ec_device {
struct device *dev;
@@ -43,6 +44,7 @@ struct wilco_ec_device {
struct resource *io_packet;
void *data_buffer;
size_t data_size;
+ struct platform_device *debugfs_pdev;
};

/**
--
2.20.1.791.gb4d0f1c61a-goog


2019-02-08 19:45:05

by Nick Crews

[permalink] [raw]
Subject: [PATCH v7 4/4] platform/chrome: rtc: Add RTC driver

This Embedded Controller has an internal RTC that is exposed
as a standard RTC class driver with read/write functionality.

The driver is added to the drivers/rtc/ so that the maintainer of that
directory will be able to comment on this change, as that maintainer is
the expert on this system. In addition, the driver code is called
indirectly after a corresponding device is registered from core.c,
as opposed to core.c registering the driver callbacks directly.

To test:
> hwclock --show --rtc /dev/rtc1
2007-12-31 16:01:20.460959-08:00
> hwclock --systohc --rtc /dev/rtc1
> hwclock --show --rtc /dev/rtc1
2018-11-29 17:08:00.780793-08:00

> hwclock --show --rtc /dev/rtc1
2007-12-31 16:01:20.460959-08:00
> hwclock --systohc --rtc /dev/rtc1
> hwclock --show --rtc /dev/rtc1
2018-11-29 17:08:00.780793-08:00

Signed-off-by: Duncan Laurie <[email protected]>
Acked-by: Enric Balletbo i Serra <[email protected]>
Acked-by: Alexandre Belloni <[email protected]>
Signed-off-by: Nick Crews <[email protected]>
---

Changes in v7: None
Changes in v6:
- In the core, actually unregister the RTC child platform_device.

Changes in v5: None
Changes in v4:
- Change me email to @chromium.org from @google.com
- Move "Add RTC driver" before "Add sysfs attributes" so that
it could get accepted earlier, since it is less contentious

Changes in v3:
- rm #define for driver name
- Don't compute weekday when reading from RTC.
Still set weekday when writing, as RTC needs this
to control advanced power scheduling
- rm check for invalid month data
- Set range_min and range_max on rtc_device

Changes in v2:
- rm license boiler plate
- rm "wilco_ec_rtc -" prefix in docstring
- Make rtc driver its own module within the drivers/rtc/ directory
- Register a rtc device from core.c that is picked up by this driver

drivers/platform/chrome/wilco_ec/core.c | 22 ++-
drivers/rtc/Kconfig | 11 ++
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-wilco-ec.c | 177 ++++++++++++++++++++++++
include/linux/platform_data/wilco-ec.h | 2 +
5 files changed, 211 insertions(+), 2 deletions(-)
create mode 100644 drivers/rtc/rtc-wilco-ec.c

diff --git a/drivers/platform/chrome/wilco_ec/core.c b/drivers/platform/chrome/wilco_ec/core.c
index ae86cae216fd..e67385ec5103 100644
--- a/drivers/platform/chrome/wilco_ec/core.c
+++ b/drivers/platform/chrome/wilco_ec/core.c
@@ -42,6 +42,7 @@ static int wilco_ec_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct wilco_ec_device *ec;
+ int ret;

ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
if (!ec)
@@ -70,21 +71,38 @@ static int wilco_ec_probe(struct platform_device *pdev)
ec->io_packet->start + EC_MAILBOX_DATA_SIZE);

/*
- * Register a debugfs platform device that will get picked up by the
- * debugfs driver. Ignore failure.
+ * Register a child device that will be found by the RTC driver.
+ * Ignore failure.
*/
ec->debugfs_pdev = platform_device_register_data(dev,
"wilco-ec-debugfs",
PLATFORM_DEVID_AUTO,
NULL, 0);

+ /* Register a child device that will be found by the RTC driver. */
+ ec->rtc_pdev = platform_device_register_data(dev, "rtc-wilco-ec",
+ PLATFORM_DEVID_AUTO,
+ NULL, 0);
+ if (IS_ERR(ec->rtc_pdev)) {
+ dev_err(dev, "Failed to create RTC platform device\n");
+ ret = PTR_ERR(ec->rtc_pdev);
+ goto unregister_debugfs;
+ }
+
return 0;
+
+unregister_debugfs:
+ if (ec->debugfs_pdev)
+ platform_device_unregister(ec->debugfs_pdev);
+ cros_ec_lpc_mec_destroy();
+ return ret;
}

static int wilco_ec_remove(struct platform_device *pdev)
{
struct wilco_ec_device *ec = platform_get_drvdata(pdev);

+ platform_device_unregister(ec->rtc_pdev);
if (ec->debugfs_pdev)
platform_device_unregister(ec->debugfs_pdev);

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 225b0b8516f3..d5063c791515 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1814,4 +1814,15 @@ config RTC_DRV_GOLDFISH
Goldfish is a code name for the virtual platform developed by Google
for Android emulation.

+config RTC_DRV_WILCO_EC
+ tristate "Wilco EC RTC"
+ depends on WILCO_EC
+ default m
+ help
+ If you say yes here, you get read/write support for the Real Time
+ Clock on the Wilco Embedded Controller (Wilco is a kind of Chromebook)
+
+ This can also be built as a module. If so, the module will
+ be named "rtc_wilco_ec".
+
endif # RTC_CLASS
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index df022d820bee..6255ea78da25 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -172,6 +172,7 @@ obj-$(CONFIG_RTC_DRV_V3020) += rtc-v3020.o
obj-$(CONFIG_RTC_DRV_VR41XX) += rtc-vr41xx.o
obj-$(CONFIG_RTC_DRV_VRTC) += rtc-mrst.o
obj-$(CONFIG_RTC_DRV_VT8500) += rtc-vt8500.o
+obj-$(CONFIG_RTC_DRV_WILCO_EC) += rtc-wilco-ec.o
obj-$(CONFIG_RTC_DRV_WM831X) += rtc-wm831x.o
obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o
obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o
diff --git a/drivers/rtc/rtc-wilco-ec.c b/drivers/rtc/rtc-wilco-ec.c
new file mode 100644
index 000000000000..35cc56114c1c
--- /dev/null
+++ b/drivers/rtc/rtc-wilco-ec.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * RTC interface for Wilco Embedded Controller with R/W abilities
+ *
+ * Copyright 2018 Google LLC
+ *
+ * The corresponding platform device is typically registered in
+ * drivers/platform/chrome/wilco_ec/core.c
+ */
+
+#include <linux/bcd.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/wilco-ec.h>
+#include <linux/rtc.h>
+#include <linux/timekeeping.h>
+
+#define EC_COMMAND_CMOS 0x7c
+#define EC_CMOS_TOD_WRITE 0x02
+#define EC_CMOS_TOD_READ 0x08
+
+/**
+ * struct ec_rtc_read - Format of RTC returned by EC.
+ * @second: Second value (0..59)
+ * @minute: Minute value (0..59)
+ * @hour: Hour value (0..23)
+ * @day: Day value (1..31)
+ * @month: Month value (1..12)
+ * @year: Year value (full year % 100)
+ * @century: Century value (full year / 100)
+ *
+ * All values are presented in binary (not BCD).
+ */
+struct ec_rtc_read {
+ u8 second;
+ u8 minute;
+ u8 hour;
+ u8 day;
+ u8 month;
+ u8 year;
+ u8 century;
+} __packed;
+
+/**
+ * struct ec_rtc_write - Format of RTC sent to the EC.
+ * @param: EC_CMOS_TOD_WRITE
+ * @century: Century value (full year / 100)
+ * @year: Year value (full year % 100)
+ * @month: Month value (1..12)
+ * @day: Day value (1..31)
+ * @hour: Hour value (0..23)
+ * @minute: Minute value (0..59)
+ * @second: Second value (0..59)
+ * @weekday: Day of the week (0=Saturday)
+ *
+ * All values are presented in BCD.
+ */
+struct ec_rtc_write {
+ u8 param;
+ u8 century;
+ u8 year;
+ u8 month;
+ u8 day;
+ u8 hour;
+ u8 minute;
+ u8 second;
+ u8 weekday;
+} __packed;
+
+int wilco_ec_rtc_read(struct device *dev, struct rtc_time *tm)
+{
+ struct wilco_ec_device *ec = dev_get_drvdata(dev->parent);
+ u8 param = EC_CMOS_TOD_READ;
+ struct ec_rtc_read rtc;
+ struct wilco_ec_message msg = {
+ .type = WILCO_EC_MSG_LEGACY,
+ .flags = WILCO_EC_FLAG_RAW_RESPONSE,
+ .command = EC_COMMAND_CMOS,
+ .request_data = &param,
+ .request_size = sizeof(param),
+ .response_data = &rtc,
+ .response_size = sizeof(rtc),
+ };
+ int ret;
+
+ ret = wilco_ec_mailbox(ec, &msg);
+ if (ret < 0)
+ return ret;
+
+ tm->tm_sec = rtc.second;
+ tm->tm_min = rtc.minute;
+ tm->tm_hour = rtc.hour;
+ tm->tm_mday = rtc.day;
+ tm->tm_mon = rtc.month - 1;
+ tm->tm_year = rtc.year + (rtc.century * 100) - 1900;
+ tm->tm_yday = rtc_year_days(tm->tm_mday, tm->tm_mon, tm->tm_year);
+
+ /* Don't compute day of week, we don't need it. */
+ tm->tm_wday = -1;
+
+ return 0;
+}
+
+int wilco_ec_rtc_write(struct device *dev, struct rtc_time *tm)
+{
+ struct wilco_ec_device *ec = dev_get_drvdata(dev->parent);
+ struct ec_rtc_write rtc;
+ struct wilco_ec_message msg = {
+ .type = WILCO_EC_MSG_LEGACY,
+ .flags = WILCO_EC_FLAG_RAW_RESPONSE,
+ .command = EC_COMMAND_CMOS,
+ .request_data = &rtc,
+ .request_size = sizeof(rtc),
+ };
+ int year = tm->tm_year + 1900;
+ /*
+ * Convert from 0=Sunday to 0=Saturday for the EC
+ * We DO need to set weekday because the EC controls battery charging
+ * schedules that depend on the day of the week.
+ */
+ int wday = tm->tm_wday == 6 ? 0 : tm->tm_wday + 1;
+ int ret;
+
+ rtc.param = EC_CMOS_TOD_WRITE;
+ rtc.century = bin2bcd(year / 100);
+ rtc.year = bin2bcd(year % 100);
+ rtc.month = bin2bcd(tm->tm_mon + 1);
+ rtc.day = bin2bcd(tm->tm_mday);
+ rtc.hour = bin2bcd(tm->tm_hour);
+ rtc.minute = bin2bcd(tm->tm_min);
+ rtc.second = bin2bcd(tm->tm_sec);
+ rtc.weekday = bin2bcd(wday);
+
+ ret = wilco_ec_mailbox(ec, &msg);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static const struct rtc_class_ops wilco_ec_rtc_ops = {
+ .read_time = wilco_ec_rtc_read,
+ .set_time = wilco_ec_rtc_write,
+};
+
+static int wilco_ec_rtc_probe(struct platform_device *pdev)
+{
+ struct rtc_device *rtc;
+
+ rtc = devm_rtc_allocate_device(&pdev->dev);
+ if (IS_ERR(rtc))
+ return PTR_ERR(rtc);
+
+ rtc->ops = &wilco_ec_rtc_ops;
+ /* EC only supports this century */
+ rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
+ rtc->range_max = RTC_TIMESTAMP_END_2099;
+ rtc->owner = THIS_MODULE;
+
+ return rtc_register_device(rtc);
+}
+
+static struct platform_driver wilco_ec_rtc_driver = {
+ .driver = {
+ .name = "rtc-wilco-ec",
+ },
+ .probe = wilco_ec_rtc_probe,
+};
+
+module_platform_driver(wilco_ec_rtc_driver);
+
+MODULE_ALIAS("platform:rtc-wilco-ec");
+MODULE_AUTHOR("Nick Crews <[email protected]>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Wilco EC RTC driver");
diff --git a/include/linux/platform_data/wilco-ec.h b/include/linux/platform_data/wilco-ec.h
index 5344975afa1a..446473a46b88 100644
--- a/include/linux/platform_data/wilco-ec.h
+++ b/include/linux/platform_data/wilco-ec.h
@@ -35,6 +35,7 @@
* is used to hold the request and the response.
* @data_size: Size of the data buffer used for EC communication.
* @debugfs_pdev: The child platform_device used by the debugfs sub-driver.
+ * @rtc_pdev: The child platform_device used by the RTC sub-driver.
*/
struct wilco_ec_device {
struct device *dev;
@@ -45,6 +46,7 @@ struct wilco_ec_device {
void *data_buffer;
size_t data_size;
struct platform_device *debugfs_pdev;
+ struct platform_device *rtc_pdev;
};

/**
--
2.20.1.791.gb4d0f1c61a-goog


2019-02-08 19:45:07

by Nick Crews

[permalink] [raw]
Subject: [PATCH v7 1/4] cros_ec: Remove cros_ec dependency in lpc_mec

In order to allow this code to be re-used, remove the dependency
on the rest of the cros_ec code from the cros_ec_lpc_mec functions.

Instead of using a hardcoded register base address of 0x800 have
this be passed in to cros_ec_lpc_mec_init(). The existing cros_ec
use case now passes in the 0x800 base address this way.

There are some error checks that happen in cros_ec_lpc_mec_in_range()
that probably shouldn't be there, as they are checking kernel-space
callers and not user-space input. However, we'll just do the refactor in
this patch, and in a future patch might remove this error checking and
fix all the instances of code that calls this.

There's a similar problem in cros_ec_lpc_read_bytes(), where we return a
checksum, but on error just return 0. This should probably be changed so
that it returns int, but we don't want to have to mess with all the
calling code for this fix. Maybe we'll come back through later and fix
this.

Signed-off-by: Duncan Laurie <[email protected]>
Acked-for-chrome-platform-by: Enric Balletbo i Serra <[email protected]>
Signed-off-by: Nick Crews <[email protected]>
---

Changes in v7: None
Changes in v6: None
Changes in v5: None
Changes in v4: None
Changes in v3:
- Change <= to >= in mec_in_range()
- Add " - EC_HOST_CMD_REGION0" to offset arg for io_bytes_mec()

Changes in v2:
- Fixed kernel-doc comments
- Fixed include of linux/mfd/cros_ec_lpc_mec.h
- cros_ec_lpc_mec_in_range() returns -EINVAL on error
- Added parens around macro variables

drivers/platform/chrome/cros_ec_lpc_mec.c | 52 +++++++++++++++++++----
drivers/platform/chrome/cros_ec_lpc_mec.h | 43 ++++++++++---------
drivers/platform/chrome/cros_ec_lpc_reg.c | 47 +++++++-------------
3 files changed, 83 insertions(+), 59 deletions(-)

diff --git a/drivers/platform/chrome/cros_ec_lpc_mec.c b/drivers/platform/chrome/cros_ec_lpc_mec.c
index c4edfa83e493..782e238a8d4e 100644
--- a/drivers/platform/chrome/cros_ec_lpc_mec.c
+++ b/drivers/platform/chrome/cros_ec_lpc_mec.c
@@ -23,7 +23,6 @@

#include <linux/delay.h>
#include <linux/io.h>
-#include <linux/mfd/cros_ec_commands.h>
#include <linux/mutex.h>
#include <linux/types.h>

@@ -34,6 +33,7 @@
* EC mutex because memmap data may be accessed without it being held.
*/
static struct mutex io_mutex;
+static u16 mec_emi_base, mec_emi_end;

/*
* cros_ec_lpc_mec_emi_write_address
@@ -46,10 +46,37 @@ static struct mutex io_mutex;
static void cros_ec_lpc_mec_emi_write_address(u16 addr,
enum cros_ec_lpc_mec_emi_access_mode access_type)
{
- /* Address relative to start of EMI range */
- addr -= MEC_EMI_RANGE_START;
- outb((addr & 0xfc) | access_type, MEC_EMI_EC_ADDRESS_B0);
- outb((addr >> 8) & 0x7f, MEC_EMI_EC_ADDRESS_B1);
+ outb((addr & 0xfc) | access_type, MEC_EMI_EC_ADDRESS_B0(mec_emi_base));
+ outb((addr >> 8) & 0x7f, MEC_EMI_EC_ADDRESS_B1(mec_emi_base));
+}
+
+/**
+ * cros_ec_lpc_mec_in_range() - Determine if addresses are in MEC EMI range.
+ *
+ * @offset: Address offset
+ * @length: Number of bytes to check
+ *
+ * Return: 1 if in range, 0 if not, and -EINVAL on failure
+ * such as the mec range not being initialized
+ */
+int cros_ec_lpc_mec_in_range(unsigned int offset, unsigned int length)
+{
+ if (length == 0)
+ return -EINVAL;
+
+ if (WARN_ON(mec_emi_base == 0 || mec_emi_end == 0))
+ return -EINVAL;
+
+ if (offset >= mec_emi_base && offset < mec_emi_end) {
+ if (WARN_ON(offset + length - 1 >= mec_emi_end))
+ return -EINVAL;
+ return 1;
+ }
+
+ if (WARN_ON(offset + length > mec_emi_base && offset < mec_emi_end))
+ return -EINVAL;
+
+ return 0;
}

/*
@@ -71,6 +98,11 @@ u8 cros_ec_lpc_io_bytes_mec(enum cros_ec_lpc_mec_io_type io_type,
u8 sum = 0;
enum cros_ec_lpc_mec_emi_access_mode access, new_access;

+ /* Return checksum of 0 if window is not initialized */
+ WARN_ON(mec_emi_base == 0 || mec_emi_end == 0);
+ if (mec_emi_base == 0 || mec_emi_end == 0)
+ return 0;
+
/*
* Long access cannot be used on misaligned data since reading B0 loads
* the data register and writing B3 flushes.
@@ -86,9 +118,9 @@ u8 cros_ec_lpc_io_bytes_mec(enum cros_ec_lpc_mec_io_type io_type,
cros_ec_lpc_mec_emi_write_address(offset, access);

/* Skip bytes in case of misaligned offset */
- io_addr = MEC_EMI_EC_DATA_B0 + (offset & 0x3);
+ io_addr = MEC_EMI_EC_DATA_B0(mec_emi_base) + (offset & 0x3);
while (i < length) {
- while (io_addr <= MEC_EMI_EC_DATA_B3) {
+ while (io_addr <= MEC_EMI_EC_DATA_B3(mec_emi_base)) {
if (io_type == MEC_IO_READ)
buf[i] = inb(io_addr++);
else
@@ -118,7 +150,7 @@ u8 cros_ec_lpc_io_bytes_mec(enum cros_ec_lpc_mec_io_type io_type,
}

/* Access [B0, B3] on each loop pass */
- io_addr = MEC_EMI_EC_DATA_B0;
+ io_addr = MEC_EMI_EC_DATA_B0(mec_emi_base);
}

done:
@@ -128,9 +160,11 @@ u8 cros_ec_lpc_io_bytes_mec(enum cros_ec_lpc_mec_io_type io_type,
}
EXPORT_SYMBOL(cros_ec_lpc_io_bytes_mec);

-void cros_ec_lpc_mec_init(void)
+void cros_ec_lpc_mec_init(unsigned int base, unsigned int end)
{
mutex_init(&io_mutex);
+ mec_emi_base = base;
+ mec_emi_end = end;
}
EXPORT_SYMBOL(cros_ec_lpc_mec_init);

diff --git a/drivers/platform/chrome/cros_ec_lpc_mec.h b/drivers/platform/chrome/cros_ec_lpc_mec.h
index 105068c0e919..896fffd174b3 100644
--- a/drivers/platform/chrome/cros_ec_lpc_mec.h
+++ b/drivers/platform/chrome/cros_ec_lpc_mec.h
@@ -24,8 +24,6 @@
#ifndef __CROS_EC_LPC_MEC_H
#define __CROS_EC_LPC_MEC_H

-#include <linux/mfd/cros_ec_commands.h>
-
enum cros_ec_lpc_mec_emi_access_mode {
/* 8-bit access */
ACCESS_TYPE_BYTE = 0x0,
@@ -45,27 +43,23 @@ enum cros_ec_lpc_mec_io_type {
MEC_IO_WRITE,
};

-/* Access IO ranges 0x800 thru 0x9ff using EMI interface instead of LPC */
-#define MEC_EMI_RANGE_START EC_HOST_CMD_REGION0
-#define MEC_EMI_RANGE_END (EC_LPC_ADDR_MEMMAP + EC_MEMMAP_SIZE)
-
/* EMI registers are relative to base */
-#define MEC_EMI_BASE 0x800
-#define MEC_EMI_HOST_TO_EC (MEC_EMI_BASE + 0)
-#define MEC_EMI_EC_TO_HOST (MEC_EMI_BASE + 1)
-#define MEC_EMI_EC_ADDRESS_B0 (MEC_EMI_BASE + 2)
-#define MEC_EMI_EC_ADDRESS_B1 (MEC_EMI_BASE + 3)
-#define MEC_EMI_EC_DATA_B0 (MEC_EMI_BASE + 4)
-#define MEC_EMI_EC_DATA_B1 (MEC_EMI_BASE + 5)
-#define MEC_EMI_EC_DATA_B2 (MEC_EMI_BASE + 6)
-#define MEC_EMI_EC_DATA_B3 (MEC_EMI_BASE + 7)
+#define MEC_EMI_HOST_TO_EC(MEC_EMI_BASE) ((MEC_EMI_BASE) + 0)
+#define MEC_EMI_EC_TO_HOST(MEC_EMI_BASE) ((MEC_EMI_BASE) + 1)
+#define MEC_EMI_EC_ADDRESS_B0(MEC_EMI_BASE) ((MEC_EMI_BASE) + 2)
+#define MEC_EMI_EC_ADDRESS_B1(MEC_EMI_BASE) ((MEC_EMI_BASE) + 3)
+#define MEC_EMI_EC_DATA_B0(MEC_EMI_BASE) ((MEC_EMI_BASE) + 4)
+#define MEC_EMI_EC_DATA_B1(MEC_EMI_BASE) ((MEC_EMI_BASE) + 5)
+#define MEC_EMI_EC_DATA_B2(MEC_EMI_BASE) ((MEC_EMI_BASE) + 6)
+#define MEC_EMI_EC_DATA_B3(MEC_EMI_BASE) ((MEC_EMI_BASE) + 7)

-/*
- * cros_ec_lpc_mec_init
+/**
+ * cros_ec_lpc_mec_init() - Initialize MEC I/O.
*
- * Initialize MEC I/O.
+ * @base: MEC EMI Base address
+ * @end: MEC EMI End address
*/
-void cros_ec_lpc_mec_init(void);
+void cros_ec_lpc_mec_init(unsigned int base, unsigned int end);

/*
* cros_ec_lpc_mec_destroy
@@ -74,6 +68,17 @@ void cros_ec_lpc_mec_init(void);
*/
void cros_ec_lpc_mec_destroy(void);

+/**
+ * cros_ec_lpc_mec_in_range() - Determine if addresses are in MEC EMI range.
+ *
+ * @offset: Address offset
+ * @length: Number of bytes to check
+ *
+ * Return: 1 if in range, 0 if not, and -EINVAL on failure
+ * such as the mec range not being initialized
+ */
+int cros_ec_lpc_mec_in_range(unsigned int offset, unsigned int length);
+
/**
* cros_ec_lpc_io_bytes_mec - Read / write bytes to MEC EMI port
*
diff --git a/drivers/platform/chrome/cros_ec_lpc_reg.c b/drivers/platform/chrome/cros_ec_lpc_reg.c
index fc23d535c404..1d9f3b7ffb3e 100644
--- a/drivers/platform/chrome/cros_ec_lpc_reg.c
+++ b/drivers/platform/chrome/cros_ec_lpc_reg.c
@@ -59,51 +59,36 @@ static u8 lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg)

u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest)
{
- if (length == 0)
- return 0;
-
- /* Access desired range through EMI interface */
- if (offset >= MEC_EMI_RANGE_START && offset <= MEC_EMI_RANGE_END) {
- /* Ensure we don't straddle EMI region */
- if (WARN_ON(offset + length - 1 > MEC_EMI_RANGE_END))
- return 0;
+ int in_range = cros_ec_lpc_mec_in_range(offset, length);

- return cros_ec_lpc_io_bytes_mec(MEC_IO_READ, offset, length,
- dest);
- }
-
- if (WARN_ON(offset + length > MEC_EMI_RANGE_START &&
- offset < MEC_EMI_RANGE_START))
+ if (in_range < 0)
return 0;

- return lpc_read_bytes(offset, length, dest);
+ 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)
{
- if (length == 0)
- return 0;
-
- /* Access desired range through EMI interface */
- if (offset >= MEC_EMI_RANGE_START && offset <= MEC_EMI_RANGE_END) {
- /* Ensure we don't straddle EMI region */
- if (WARN_ON(offset + length - 1 > MEC_EMI_RANGE_END))
- return 0;
+ int in_range = cros_ec_lpc_mec_in_range(offset, length);

- return cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, offset, length,
- msg);
- }
-
- if (WARN_ON(offset + length > MEC_EMI_RANGE_START &&
- offset < MEC_EMI_RANGE_START))
+ if (in_range < 0)
return 0;

- return lpc_write_bytes(offset, length, msg);
+ 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();
+ cros_ec_lpc_mec_init(EC_HOST_CMD_REGION0,
+ EC_LPC_ADDR_MEMMAP + EC_MEMMAP_SIZE);
}

void cros_ec_lpc_reg_destroy(void)
--
2.20.1.791.gb4d0f1c61a-goog


2019-02-08 19:45:09

by Nick Crews

[permalink] [raw]
Subject: [PATCH v7 2/4] platform/chrome: Add new driver for Wilco EC

This EC is an incompatible variant of the typical Chrome OS embedded
controller. It uses the same low-level communication and a similar
protocol with some significant differences. The EC firmware does
not support the same mailbox commands so it is not registered as a
cros_ec device type. This commit exports the wilco_ec_mailbox()
function so that other modules can use it to communicate with the EC.

Signed-off-by: Duncan Laurie <[email protected]>
Signed-off-by: Nick Crews <[email protected]>
---

Changes in v7: None
Changes in v6:
- Re-added WILCO_EC_FLAG_EXTENDED_DATA and went back to always
reading either EC_MAILBOX_DATA_SIZE or EC_MAILBOX_DATA_SIZE_EXTENDED
bytes, since it turns out the EC always expects you to calculate the
checksum over all of them.
- Split WILCO_EC_MSG_TELEMETRY into WILCO_EC_MSG_TELEMETRY_SHORT and
WILCO_EC_MSG_TELEMETRY_LONG, since there will be two different
commands.
- A couple comment and err message polishes

Changes in v5:
- move checking of NO_RESPONSE flag before timeout check,
so now timeout doesn't always happen when EC isn't supposed
to respond.
- rm WILCO_EC_FLAG_EXTENDED_DATA, that is already obvious from
wilco_ec_message.response_size

Changes in v4:
- add #define DRV_NAME
- remove redundant Kconfig nesting
- changed my email to @chromium.org
- Add better explanation of what core.c does

Changes in v3:
- remove unused ret in probe()
- Add newline spacer in probe()
- rm unnecessary res in get_resource()
- s/8bit/8-bit
- rm first sleep when sending command to EC

Changes in v2:
- Remove COMPILE_TEST from Kconfig because inb()/outb()
won't work on anything but X86
- Add myself as module author
- Tweak mailbox()

drivers/platform/chrome/Kconfig | 4 +-
drivers/platform/chrome/Makefile | 2 +
drivers/platform/chrome/wilco_ec/Kconfig | 11 +
drivers/platform/chrome/wilco_ec/Makefile | 4 +
drivers/platform/chrome/wilco_ec/core.c | 104 +++++++++
drivers/platform/chrome/wilco_ec/mailbox.c | 236 +++++++++++++++++++++
include/linux/platform_data/wilco-ec.h | 140 ++++++++++++
7 files changed, 500 insertions(+), 1 deletion(-)
create mode 100644 drivers/platform/chrome/wilco_ec/Kconfig
create mode 100644 drivers/platform/chrome/wilco_ec/Makefile
create mode 100644 drivers/platform/chrome/wilco_ec/core.c
create mode 100644 drivers/platform/chrome/wilco_ec/mailbox.c
create mode 100644 include/linux/platform_data/wilco-ec.h

diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig
index 16b1615958aa..bf889adfd4ef 100644
--- a/drivers/platform/chrome/Kconfig
+++ b/drivers/platform/chrome/Kconfig
@@ -49,6 +49,8 @@ config CHROMEOS_TBMC
To compile this driver as a module, choose M here: the
module will be called chromeos_tbmc.

+source "drivers/platform/chrome/wilco_ec/Kconfig"
+
config CROS_EC_CTL
tristate

@@ -86,7 +88,7 @@ config CROS_EC_LPC

config CROS_EC_LPC_MEC
bool "ChromeOS Embedded Controller LPC Microchip EC (MEC) variant"
- depends on CROS_EC_LPC
+ depends on CROS_EC_LPC || WILCO_EC
default n
help
If you say Y here, a variant LPC protocol for the Microchip EC
diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile
index cd591bf872bb..7242ee2b13c5 100644
--- a/drivers/platform/chrome/Makefile
+++ b/drivers/platform/chrome/Makefile
@@ -13,3 +13,5 @@ cros_ec_lpcs-$(CONFIG_CROS_EC_LPC_MEC) += cros_ec_lpc_mec.o
obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpcs.o
obj-$(CONFIG_CROS_EC_PROTO) += cros_ec_proto.o
obj-$(CONFIG_CROS_KBD_LED_BACKLIGHT) += cros_kbd_led_backlight.o
+
+obj-$(CONFIG_WILCO_EC) += wilco_ec/
diff --git a/drivers/platform/chrome/wilco_ec/Kconfig b/drivers/platform/chrome/wilco_ec/Kconfig
new file mode 100644
index 000000000000..20945a301ec6
--- /dev/null
+++ b/drivers/platform/chrome/wilco_ec/Kconfig
@@ -0,0 +1,11 @@
+config WILCO_EC
+ tristate "ChromeOS Wilco Embedded Controller"
+ depends on ACPI && X86
+ select CROS_EC_LPC_MEC
+ 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
+ with a checksum.
+
+ To compile this driver as a module, choose M here: the
+ module will be called wilco_ec.
diff --git a/drivers/platform/chrome/wilco_ec/Makefile b/drivers/platform/chrome/wilco_ec/Makefile
new file mode 100644
index 000000000000..03b32301dc61
--- /dev/null
+++ b/drivers/platform/chrome/wilco_ec/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+
+wilco_ec-objs := core.o mailbox.o
+obj-$(CONFIG_WILCO_EC) += wilco_ec.o
diff --git a/drivers/platform/chrome/wilco_ec/core.c b/drivers/platform/chrome/wilco_ec/core.c
new file mode 100644
index 000000000000..20ecc580d108
--- /dev/null
+++ b/drivers/platform/chrome/wilco_ec/core.c
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Core driver for Wilco Embedded Controller
+ *
+ * Copyright 2018 Google LLC
+ *
+ * This is the entry point for the drivers that control the Wilco EC.
+ * This driver is responsible for several tasks:
+ * - Initialize the register interface that is used by wilco_ec_mailbox()
+ * - Create a platform device which is picked up by the debugfs driver
+ * - Create a platform device which is picked up by the RTC driver
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/platform_data/wilco-ec.h>
+#include <linux/platform_device.h>
+
+#include "../cros_ec_lpc_mec.h"
+
+#define DRV_NAME "wilco-ec"
+
+static struct resource *wilco_get_resource(struct platform_device *pdev,
+ int index)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_IO, index);
+ if (!res) {
+ dev_dbg(dev, "Couldn't find IO resource %d\n", index);
+ return res;
+ }
+
+ return devm_request_region(dev, res->start, resource_size(res),
+ dev_name(dev));
+}
+
+static int wilco_ec_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct wilco_ec_device *ec;
+
+ ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
+ if (!ec)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, ec);
+ 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_buffer = devm_kzalloc(dev, ec->data_size, GFP_KERNEL);
+ if (!ec->data_buffer)
+ return -ENOMEM;
+
+ /* Prepare access to IO regions provided by ACPI */
+ ec->io_data = wilco_get_resource(pdev, 0); /* Host Data */
+ ec->io_command = wilco_get_resource(pdev, 1); /* Host Command */
+ ec->io_packet = wilco_get_resource(pdev, 2); /* MEC EMI */
+ if (!ec->io_data || !ec->io_command || !ec->io_packet)
+ return -ENODEV;
+
+ /* Initialize cros_ec register interface for communication */
+ cros_ec_lpc_mec_init(ec->io_packet->start,
+ ec->io_packet->start + EC_MAILBOX_DATA_SIZE);
+
+ return 0;
+}
+
+static int wilco_ec_remove(struct platform_device *pdev)
+{
+ /* Teardown cros_ec interface */
+ cros_ec_lpc_mec_destroy();
+
+ return 0;
+}
+
+static const struct acpi_device_id wilco_ec_acpi_device_ids[] = {
+ { "GOOG000C", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, wilco_ec_acpi_device_ids);
+
+static struct platform_driver wilco_ec_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .acpi_match_table = wilco_ec_acpi_device_ids,
+ },
+ .probe = wilco_ec_probe,
+ .remove = wilco_ec_remove,
+};
+
+module_platform_driver(wilco_ec_driver);
+
+MODULE_AUTHOR("Nick Crews <[email protected]>");
+MODULE_AUTHOR("Duncan Laurie <[email protected]>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("ChromeOS Wilco Embedded Controller driver");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/drivers/platform/chrome/wilco_ec/mailbox.c b/drivers/platform/chrome/wilco_ec/mailbox.c
new file mode 100644
index 000000000000..c7028488e575
--- /dev/null
+++ b/drivers/platform/chrome/wilco_ec/mailbox.c
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Mailbox interface for Wilco Embedded Controller
+ *
+ * Copyright 2018 Google LLC
+ *
+ * The Wilco EC is similar to a typical ChromeOS embedded controller.
+ * It uses the same MEC based low-level communication and a similar
+ * protocol, but with some important differences. The EC firmware does
+ * not support the same mailbox commands so it is not registered as a
+ * cros_ec device type.
+ *
+ * Most messages follow a standard format, but there are some exceptions
+ * and an interface is provided to do direct/raw transactions that do not
+ * make assumptions about byte placement.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/platform_data/wilco-ec.h>
+#include <linux/platform_device.h>
+
+#include "../cros_ec_lpc_mec.h"
+
+/* Version of mailbox interface */
+#define EC_MAILBOX_VERSION 0
+
+/* Command to start mailbox transaction */
+#define EC_MAILBOX_START_COMMAND 0xda
+
+/* Version of EC protocol */
+#define EC_MAILBOX_PROTO_VERSION 3
+
+/* Number of header bytes to be counted as data bytes */
+#define EC_MAILBOX_DATA_EXTRA 2
+
+/* Maximum timeout */
+#define EC_MAILBOX_TIMEOUT HZ
+
+/* EC response flags */
+#define EC_CMDR_DATA BIT(0) /* Data ready for host to read */
+#define EC_CMDR_PENDING BIT(1) /* Write pending to EC */
+#define EC_CMDR_BUSY BIT(2) /* EC is busy processing a command */
+#define EC_CMDR_CMD BIT(3) /* Last host write was a command */
+
+/**
+ * wilco_ec_response_timed_out() - Wait for EC response.
+ * @ec: EC device.
+ *
+ * Return: true if EC timed out, false if EC did not time out.
+ */
+static bool wilco_ec_response_timed_out(struct wilco_ec_device *ec)
+{
+ unsigned long timeout = jiffies + EC_MAILBOX_TIMEOUT;
+
+ do {
+ if (!(inb(ec->io_command->start) &
+ (EC_CMDR_PENDING | EC_CMDR_BUSY)))
+ return false;
+ usleep_range(100, 200);
+ } while (time_before(jiffies, timeout));
+
+ return true;
+}
+
+/**
+ * wilco_ec_checksum() - Compute 8-bit checksum over data range.
+ * @data: Data to checksum.
+ * @size: Number of bytes to checksum.
+ *
+ * Return: 8-bit checksum of provided data.
+ */
+static u8 wilco_ec_checksum(const void *data, size_t size)
+{
+ u8 *data_bytes = (u8 *)data;
+ u8 checksum = 0;
+ size_t i;
+
+ for (i = 0; i < size; i++)
+ checksum += data_bytes[i];
+
+ return checksum;
+}
+
+/**
+ * wilco_ec_prepare() - Prepare the request structure for the EC.
+ * @msg: EC message with request information.
+ * @rq: EC request structure to fill.
+ */
+static void wilco_ec_prepare(struct wilco_ec_message *msg,
+ struct wilco_ec_request *rq)
+{
+ memset(rq, 0, sizeof(*rq));
+
+ /* Handle messages without trimming bytes from the request */
+ if (msg->request_size && msg->flags & WILCO_EC_FLAG_RAW_REQUEST) {
+ rq->reserved_raw = *(u8 *)msg->request_data;
+ msg->request_size--;
+ memmove(msg->request_data, msg->request_data + 1,
+ msg->request_size);
+ }
+
+ /* Fill in request packet */
+ rq->struct_version = EC_MAILBOX_PROTO_VERSION;
+ rq->mailbox_id = msg->type;
+ rq->mailbox_version = EC_MAILBOX_VERSION;
+ rq->data_size = msg->request_size + EC_MAILBOX_DATA_EXTRA;
+ rq->command = msg->command;
+
+ /* Checksum header and data */
+ rq->checksum = wilco_ec_checksum(rq, sizeof(*rq));
+ rq->checksum += wilco_ec_checksum(msg->request_data, msg->request_size);
+ rq->checksum = -rq->checksum;
+}
+
+/**
+ * wilco_ec_transfer() - Perform actual data transfer.
+ * @ec: EC device.
+ * @msg: EC message data for request and response.
+ * @rq: Filled in request structure
+ *
+ * Context: ec->mailbox_lock should be held while using this function.
+ * Return: number of bytes received or negative error code on failure.
+ */
+int wilco_ec_transfer(struct wilco_ec_device *ec, struct wilco_ec_message *msg,
+ struct wilco_ec_request *rq)
+{
+ 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);
+ cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, sizeof(*rq), msg->request_size,
+ msg->request_data);
+
+ /* Start the command */
+ outb(EC_MAILBOX_START_COMMAND, ec->io_command->start);
+
+ /* For some commands (eg shutdown) the EC will not respond, that's OK */
+ if (msg->flags & WILCO_EC_FLAG_NO_RESPONSE) {
+ dev_dbg(ec->dev, "EC does not respond to this command\n");
+ return 0;
+ }
+
+ /* Wait for it to complete */
+ if (wilco_ec_response_timed_out(ec)) {
+ dev_dbg(ec->dev, "response timed out\n");
+ return -ETIMEDOUT;
+ }
+
+ /* Check result */
+ flag = inb(ec->io_data->start);
+ if (flag) {
+ dev_dbg(ec->dev, "bad response: 0x%02x\n", flag);
+ return -EIO;
+ }
+
+ 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);
+ if (checksum) {
+ dev_dbg(ec->dev, "bad packet checksum 0x%02x\n", rs->checksum);
+ return -EBADMSG;
+ }
+
+ /* Check that the EC reported success */
+ msg->result = rs->result;
+ if (msg->result) {
+ dev_dbg(ec->dev, "bad response: 0x%02x\n", msg->result);
+ return -EBADMSG;
+ }
+
+ /* Check the returned data size, skipping the header */
+ if (rs->data_size != size) {
+ dev_dbg(ec->dev, "unexpected packet size (%u != %zu)",
+ rs->data_size, size);
+ return -EMSGSIZE;
+ }
+
+ /* Skip 1 response data byte unless specified */
+ size = (msg->flags & WILCO_EC_FLAG_RAW_RESPONSE) ? 0 : 1;
+ if ((ssize_t) rs->data_size - size < msg->response_size) {
+ dev_dbg(ec->dev, "response data too short (%zd < %zu)",
+ (ssize_t) rs->data_size - size, msg->response_size);
+ return -EMSGSIZE;
+ }
+
+ /* Ignore response data bytes as requested */
+ memcpy(msg->response_data, rs->data + size, msg->response_size);
+
+ /* Return actual amount of data received */
+ return msg->response_size;
+}
+
+/**
+ * wilco_ec_mailbox() - Send EC request and receive EC response.
+ * @ec: EC device.
+ * @msg: EC message data for request and response.
+ *
+ * On entry msg->type, msg->flags, msg->command, msg->request_size,
+ * msg->response_size, and msg->request_data should all be filled in.
+ *
+ * On exit msg->result and msg->response_data will be filled.
+ *
+ * Return: number of bytes received or negative error code on failure.
+ */
+int wilco_ec_mailbox(struct wilco_ec_device *ec, struct wilco_ec_message *msg)
+{
+ struct wilco_ec_request *rq;
+ int ret;
+
+ dev_dbg(ec->dev, "cmd=%02x type=%04x flags=%02x rslen=%zu rqlen=%zu\n",
+ msg->command, msg->type, msg->flags, msg->response_size,
+ msg->request_size);
+
+ /* Prepare request packet */
+ rq = ec->data_buffer;
+ wilco_ec_prepare(msg, rq);
+
+ mutex_lock(&ec->mailbox_lock);
+ ret = wilco_ec_transfer(ec, msg, rq);
+ mutex_unlock(&ec->mailbox_lock);
+
+ return ret;
+
+}
+EXPORT_SYMBOL_GPL(wilco_ec_mailbox);
diff --git a/include/linux/platform_data/wilco-ec.h b/include/linux/platform_data/wilco-ec.h
new file mode 100644
index 000000000000..0feb4b520a54
--- /dev/null
+++ b/include/linux/platform_data/wilco-ec.h
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ChromeOS Wilco Embedded Controller
+ *
+ * Copyright 2018 Google LLC
+ */
+
+#ifndef WILCO_EC_H
+#define WILCO_EC_H
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+
+/* Message flags for using the mailbox() interface */
+#define WILCO_EC_FLAG_NO_RESPONSE BIT(0) /* EC does not respond */
+#define WILCO_EC_FLAG_EXTENDED_DATA BIT(1) /* EC returns 256 data bytes */
+#define WILCO_EC_FLAG_RAW_REQUEST BIT(2) /* Do not trim request data */
+#define WILCO_EC_FLAG_RAW_RESPONSE BIT(3) /* Do not trim response data */
+#define WILCO_EC_FLAG_RAW (WILCO_EC_FLAG_RAW_REQUEST | \
+ WILCO_EC_FLAG_RAW_RESPONSE)
+
+/* Normal commands have a maximum 32 bytes of data */
+#define EC_MAILBOX_DATA_SIZE 32
+/* Extended commands have 256 bytes of response data */
+#define EC_MAILBOX_DATA_SIZE_EXTENDED 256
+
+/**
+ * struct wilco_ec_device - Wilco Embedded Controller handle.
+ * @dev: Device handle.
+ * @mailbox_lock: Mutex to ensure one mailbox command at a time.
+ * @io_command: I/O port for mailbox command. Provided by ACPI.
+ * @io_data: I/O port for mailbox data. Provided by ACPI.
+ * @io_packet: I/O port for mailbox packet data. Provided by ACPI.
+ * @data_buffer: Buffer used for EC communication. The same buffer
+ * is used to hold the request and the response.
+ * @data_size: Size of the data buffer used for EC communication.
+ */
+struct wilco_ec_device {
+ struct device *dev;
+ struct mutex mailbox_lock;
+ struct resource *io_command;
+ struct resource *io_data;
+ struct resource *io_packet;
+ void *data_buffer;
+ size_t data_size;
+};
+
+/**
+ * struct wilco_ec_request - Mailbox request message format.
+ * @struct_version: Should be %EC_MAILBOX_PROTO_VERSION
+ * @checksum: Sum of all bytes must be 0.
+ * @mailbox_id: Mailbox identifier, specifies the command set.
+ * @mailbox_version: Mailbox interface version %EC_MAILBOX_VERSION
+ * @reserved: Set to zero.
+ * @data_size: Length of request, data + last 2 bytes of the header.
+ * @command: Mailbox command code, unique for each mailbox_id set.
+ * @reserved_raw: Set to zero for most commands, but is used by
+ * some command types and for raw commands.
+ */
+struct wilco_ec_request {
+ u8 struct_version;
+ u8 checksum;
+ u16 mailbox_id;
+ u8 mailbox_version;
+ u8 reserved;
+ u16 data_size;
+ u8 command;
+ u8 reserved_raw;
+} __packed;
+
+/**
+ * struct wilco_ec_response - Mailbox response message format.
+ * @struct_version: Should be %EC_MAILBOX_PROTO_VERSION
+ * @checksum: Sum of all bytes must be 0.
+ * @result: Result code from the EC. Non-zero indicates an error.
+ * @data_size: Length of the response data buffer.
+ * @reserved: Set to zero.
+ * @mbox0: EC returned data at offset 0 is unused (always 0) so this byte
+ * is treated as part of the header instead of the data.
+ * @data: Response data buffer. Max size is %EC_MAILBOX_DATA_SIZE_EXTENDED.
+ */
+struct wilco_ec_response {
+ u8 struct_version;
+ u8 checksum;
+ u16 result;
+ u16 data_size;
+ u8 reserved[2];
+ u8 mbox0;
+ u8 data[0];
+} __packed;
+
+/**
+ * enum wilco_ec_msg_type - Message type to select a set of command codes.
+ * @WILCO_EC_MSG_LEGACY: Legacy EC messages for standard EC behavior.
+ * @WILCO_EC_MSG_PROPERTY: Get/Set/Sync EC controlled NVRAM property.
+ * @WILCO_EC_MSG_TELEMETRY_SHORT: 32 bytes of telemetry data provided by the EC.
+ * @WILCO_EC_MSG_TELEMETRY_LONG: 256 bytes of telemetry data provided by the EC.
+ */
+enum wilco_ec_msg_type {
+ WILCO_EC_MSG_LEGACY = 0x00f0,
+ WILCO_EC_MSG_PROPERTY = 0x00f2,
+ WILCO_EC_MSG_TELEMETRY_SHORT = 0x00f5,
+ WILCO_EC_MSG_TELEMETRY_LONG = 0x00f6,
+};
+
+/**
+ * struct wilco_ec_message - Request and response message.
+ * @type: Mailbox message type.
+ * @flags: Message flags, e.g. %WILCO_EC_FLAG_NO_RESPONSE.
+ * @command: Mailbox command code.
+ * @result: Result code from the EC. Non-zero indicates an error.
+ * @request_size: Number of bytes to send to the EC.
+ * @request_data: Buffer containing the request data.
+ * @response_size: Number of bytes expected from the EC.
+ * This is 32 by default and 256 if the flag
+ * is set for %WILCO_EC_FLAG_EXTENDED_DATA
+ * @response_data: Buffer containing the response data, should be
+ * response_size bytes and allocated by caller.
+ */
+struct wilco_ec_message {
+ enum wilco_ec_msg_type type;
+ u8 flags;
+ u8 command;
+ u8 result;
+ size_t request_size;
+ void *request_data;
+ size_t response_size;
+ void *response_data;
+};
+
+/**
+ * wilco_ec_mailbox() - Send request to the EC and receive the response.
+ * @ec: Wilco EC device.
+ * @msg: Wilco EC message.
+ *
+ * Return: Number of bytes received or negative error code on failure.
+ */
+int wilco_ec_mailbox(struct wilco_ec_device *ec, struct wilco_ec_message *msg);
+
+#endif /* WILCO_EC_H */
--
2.20.1.791.gb4d0f1c61a-goog


2019-02-09 00:41:57

by Nick Crews

[permalink] [raw]
Subject: Re: [PATCH v7 4/4] platform/chrome: rtc: Add RTC driver

Sorry all, there is one more error in here I just found, I just sent
out v8 that corrects it.
Sorry Enric if you already started trying to merge this version.

On Fri, Feb 8, 2019 at 12:38 PM Nick Crews <[email protected]> wrote:
>
> This Embedded Controller has an internal RTC that is exposed
> as a standard RTC class driver with read/write functionality.
>
> The driver is added to the drivers/rtc/ so that the maintainer of that
> directory will be able to comment on this change, as that maintainer is
> the expert on this system. In addition, the driver code is called
> indirectly after a corresponding device is registered from core.c,
> as opposed to core.c registering the driver callbacks directly.
>
> To test:
> > hwclock --show --rtc /dev/rtc1
> 2007-12-31 16:01:20.460959-08:00
> > hwclock --systohc --rtc /dev/rtc1
> > hwclock --show --rtc /dev/rtc1
> 2018-11-29 17:08:00.780793-08:00
>
> > hwclock --show --rtc /dev/rtc1
> 2007-12-31 16:01:20.460959-08:00
> > hwclock --systohc --rtc /dev/rtc1
> > hwclock --show --rtc /dev/rtc1
> 2018-11-29 17:08:00.780793-08:00
>
> Signed-off-by: Duncan Laurie <[email protected]>
> Acked-by: Enric Balletbo i Serra <[email protected]>
> Acked-by: Alexandre Belloni <[email protected]>
> Signed-off-by: Nick Crews <[email protected]>
> ---
>
> Changes in v7: None
> Changes in v6:
> - In the core, actually unregister the RTC child platform_device.
>
> Changes in v5: None
> Changes in v4:
> - Change me email to @chromium.org from @google.com
> - Move "Add RTC driver" before "Add sysfs attributes" so that
> it could get accepted earlier, since it is less contentious
>
> Changes in v3:
> - rm #define for driver name
> - Don't compute weekday when reading from RTC.
> Still set weekday when writing, as RTC needs this
> to control advanced power scheduling
> - rm check for invalid month data
> - Set range_min and range_max on rtc_device
>
> Changes in v2:
> - rm license boiler plate
> - rm "wilco_ec_rtc -" prefix in docstring
> - Make rtc driver its own module within the drivers/rtc/ directory
> - Register a rtc device from core.c that is picked up by this driver
>
> drivers/platform/chrome/wilco_ec/core.c | 22 ++-
> drivers/rtc/Kconfig | 11 ++
> drivers/rtc/Makefile | 1 +
> drivers/rtc/rtc-wilco-ec.c | 177 ++++++++++++++++++++++++
> include/linux/platform_data/wilco-ec.h | 2 +
> 5 files changed, 211 insertions(+), 2 deletions(-)
> create mode 100644 drivers/rtc/rtc-wilco-ec.c
>
> diff --git a/drivers/platform/chrome/wilco_ec/core.c b/drivers/platform/chrome/wilco_ec/core.c
> index ae86cae216fd..e67385ec5103 100644
> --- a/drivers/platform/chrome/wilco_ec/core.c
> +++ b/drivers/platform/chrome/wilco_ec/core.c
> @@ -42,6 +42,7 @@ static int wilco_ec_probe(struct platform_device *pdev)
> {
> struct device *dev = &pdev->dev;
> struct wilco_ec_device *ec;
> + int ret;
>
> ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
> if (!ec)
> @@ -70,21 +71,38 @@ static int wilco_ec_probe(struct platform_device *pdev)
> ec->io_packet->start + EC_MAILBOX_DATA_SIZE);
>
> /*
> - * Register a debugfs platform device that will get picked up by the
> - * debugfs driver. Ignore failure.
> + * Register a child device that will be found by the RTC driver.
> + * Ignore failure.

I did some sloppy duplication here. I modified this and the
previous commit to fix this.

> */
> ec->debugfs_pdev = platform_device_register_data(dev,
> "wilco-ec-debugfs",
> PLATFORM_DEVID_AUTO,
> NULL, 0);
>
> + /* Register a child device that will be found by the RTC driver. */
> + ec->rtc_pdev = platform_device_register_data(dev, "rtc-wilco-ec",
> + PLATFORM_DEVID_AUTO,
> + NULL, 0);
> + if (IS_ERR(ec->rtc_pdev)) {
> + dev_err(dev, "Failed to create RTC platform device\n");
> + ret = PTR_ERR(ec->rtc_pdev);
> + goto unregister_debugfs;
> + }
> +
> return 0;
> +
> +unregister_debugfs:
> + if (ec->debugfs_pdev)
> + platform_device_unregister(ec->debugfs_pdev);
> + cros_ec_lpc_mec_destroy();
> + return ret;
> }
>
> static int wilco_ec_remove(struct platform_device *pdev)
> {
> struct wilco_ec_device *ec = platform_get_drvdata(pdev);
>
> + platform_device_unregister(ec->rtc_pdev);
> if (ec->debugfs_pdev)
> platform_device_unregister(ec->debugfs_pdev);
>
> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> index 225b0b8516f3..d5063c791515 100644
> --- a/drivers/rtc/Kconfig
> +++ b/drivers/rtc/Kconfig
> @@ -1814,4 +1814,15 @@ config RTC_DRV_GOLDFISH
> Goldfish is a code name for the virtual platform developed by Google
> for Android emulation.
>
> +config RTC_DRV_WILCO_EC
> + tristate "Wilco EC RTC"
> + depends on WILCO_EC
> + default m
> + help
> + If you say yes here, you get read/write support for the Real Time
> + Clock on the Wilco Embedded Controller (Wilco is a kind of Chromebook)
> +
> + This can also be built as a module. If so, the module will
> + be named "rtc_wilco_ec".
> +
> endif # RTC_CLASS
> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
> index df022d820bee..6255ea78da25 100644
> --- a/drivers/rtc/Makefile
> +++ b/drivers/rtc/Makefile
> @@ -172,6 +172,7 @@ obj-$(CONFIG_RTC_DRV_V3020) += rtc-v3020.o
> obj-$(CONFIG_RTC_DRV_VR41XX) += rtc-vr41xx.o
> obj-$(CONFIG_RTC_DRV_VRTC) += rtc-mrst.o
> obj-$(CONFIG_RTC_DRV_VT8500) += rtc-vt8500.o
> +obj-$(CONFIG_RTC_DRV_WILCO_EC) += rtc-wilco-ec.o
> obj-$(CONFIG_RTC_DRV_WM831X) += rtc-wm831x.o
> obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o
> obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o
> diff --git a/drivers/rtc/rtc-wilco-ec.c b/drivers/rtc/rtc-wilco-ec.c
> new file mode 100644
> index 000000000000..35cc56114c1c
> --- /dev/null
> +++ b/drivers/rtc/rtc-wilco-ec.c
> @@ -0,0 +1,177 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * RTC interface for Wilco Embedded Controller with R/W abilities
> + *
> + * Copyright 2018 Google LLC
> + *
> + * The corresponding platform device is typically registered in
> + * drivers/platform/chrome/wilco_ec/core.c
> + */
> +
> +#include <linux/bcd.h>
> +#include <linux/err.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/platform_data/wilco-ec.h>
> +#include <linux/rtc.h>
> +#include <linux/timekeeping.h>
> +
> +#define EC_COMMAND_CMOS 0x7c
> +#define EC_CMOS_TOD_WRITE 0x02
> +#define EC_CMOS_TOD_READ 0x08
> +
> +/**
> + * struct ec_rtc_read - Format of RTC returned by EC.
> + * @second: Second value (0..59)
> + * @minute: Minute value (0..59)
> + * @hour: Hour value (0..23)
> + * @day: Day value (1..31)
> + * @month: Month value (1..12)
> + * @year: Year value (full year % 100)
> + * @century: Century value (full year / 100)
> + *
> + * All values are presented in binary (not BCD).
> + */
> +struct ec_rtc_read {
> + u8 second;
> + u8 minute;
> + u8 hour;
> + u8 day;
> + u8 month;
> + u8 year;
> + u8 century;
> +} __packed;
> +
> +/**
> + * struct ec_rtc_write - Format of RTC sent to the EC.
> + * @param: EC_CMOS_TOD_WRITE
> + * @century: Century value (full year / 100)
> + * @year: Year value (full year % 100)
> + * @month: Month value (1..12)
> + * @day: Day value (1..31)
> + * @hour: Hour value (0..23)
> + * @minute: Minute value (0..59)
> + * @second: Second value (0..59)
> + * @weekday: Day of the week (0=Saturday)
> + *
> + * All values are presented in BCD.
> + */
> +struct ec_rtc_write {
> + u8 param;
> + u8 century;
> + u8 year;
> + u8 month;
> + u8 day;
> + u8 hour;
> + u8 minute;
> + u8 second;
> + u8 weekday;
> +} __packed;
> +
> +int wilco_ec_rtc_read(struct device *dev, struct rtc_time *tm)
> +{
> + struct wilco_ec_device *ec = dev_get_drvdata(dev->parent);
> + u8 param = EC_CMOS_TOD_READ;
> + struct ec_rtc_read rtc;
> + struct wilco_ec_message msg = {
> + .type = WILCO_EC_MSG_LEGACY,
> + .flags = WILCO_EC_FLAG_RAW_RESPONSE,
> + .command = EC_COMMAND_CMOS,
> + .request_data = &param,
> + .request_size = sizeof(param),
> + .response_data = &rtc,
> + .response_size = sizeof(rtc),
> + };
> + int ret;
> +
> + ret = wilco_ec_mailbox(ec, &msg);
> + if (ret < 0)
> + return ret;
> +
> + tm->tm_sec = rtc.second;
> + tm->tm_min = rtc.minute;
> + tm->tm_hour = rtc.hour;
> + tm->tm_mday = rtc.day;
> + tm->tm_mon = rtc.month - 1;
> + tm->tm_year = rtc.year + (rtc.century * 100) - 1900;
> + tm->tm_yday = rtc_year_days(tm->tm_mday, tm->tm_mon, tm->tm_year);
> +
> + /* Don't compute day of week, we don't need it. */
> + tm->tm_wday = -1;
> +
> + return 0;
> +}
> +
> +int wilco_ec_rtc_write(struct device *dev, struct rtc_time *tm)
> +{
> + struct wilco_ec_device *ec = dev_get_drvdata(dev->parent);
> + struct ec_rtc_write rtc;
> + struct wilco_ec_message msg = {
> + .type = WILCO_EC_MSG_LEGACY,
> + .flags = WILCO_EC_FLAG_RAW_RESPONSE,
> + .command = EC_COMMAND_CMOS,
> + .request_data = &rtc,
> + .request_size = sizeof(rtc),
> + };
> + int year = tm->tm_year + 1900;
> + /*
> + * Convert from 0=Sunday to 0=Saturday for the EC
> + * We DO need to set weekday because the EC controls battery charging
> + * schedules that depend on the day of the week.
> + */
> + int wday = tm->tm_wday == 6 ? 0 : tm->tm_wday + 1;
> + int ret;
> +
> + rtc.param = EC_CMOS_TOD_WRITE;
> + rtc.century = bin2bcd(year / 100);
> + rtc.year = bin2bcd(year % 100);
> + rtc.month = bin2bcd(tm->tm_mon + 1);
> + rtc.day = bin2bcd(tm->tm_mday);
> + rtc.hour = bin2bcd(tm->tm_hour);
> + rtc.minute = bin2bcd(tm->tm_min);
> + rtc.second = bin2bcd(tm->tm_sec);
> + rtc.weekday = bin2bcd(wday);
> +
> + ret = wilco_ec_mailbox(ec, &msg);
> + if (ret < 0)
> + return ret;
> +
> + return 0;
> +}
> +
> +static const struct rtc_class_ops wilco_ec_rtc_ops = {
> + .read_time = wilco_ec_rtc_read,
> + .set_time = wilco_ec_rtc_write,
> +};
> +
> +static int wilco_ec_rtc_probe(struct platform_device *pdev)
> +{
> + struct rtc_device *rtc;
> +
> + rtc = devm_rtc_allocate_device(&pdev->dev);
> + if (IS_ERR(rtc))
> + return PTR_ERR(rtc);
> +
> + rtc->ops = &wilco_ec_rtc_ops;
> + /* EC only supports this century */
> + rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
> + rtc->range_max = RTC_TIMESTAMP_END_2099;
> + rtc->owner = THIS_MODULE;
> +
> + return rtc_register_device(rtc);
> +}
> +
> +static struct platform_driver wilco_ec_rtc_driver = {
> + .driver = {
> + .name = "rtc-wilco-ec",
> + },
> + .probe = wilco_ec_rtc_probe,
> +};
> +
> +module_platform_driver(wilco_ec_rtc_driver);
> +
> +MODULE_ALIAS("platform:rtc-wilco-ec");
> +MODULE_AUTHOR("Nick Crews <[email protected]>");
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("Wilco EC RTC driver");
> diff --git a/include/linux/platform_data/wilco-ec.h b/include/linux/platform_data/wilco-ec.h
> index 5344975afa1a..446473a46b88 100644
> --- a/include/linux/platform_data/wilco-ec.h
> +++ b/include/linux/platform_data/wilco-ec.h
> @@ -35,6 +35,7 @@
> * is used to hold the request and the response.
> * @data_size: Size of the data buffer used for EC communication.
> * @debugfs_pdev: The child platform_device used by the debugfs sub-driver.
> + * @rtc_pdev: The child platform_device used by the RTC sub-driver.
> */
> struct wilco_ec_device {
> struct device *dev;
> @@ -45,6 +46,7 @@ struct wilco_ec_device {
> void *data_buffer;
> size_t data_size;
> struct platform_device *debugfs_pdev;
> + struct platform_device *rtc_pdev;
> };
>
> /**
> --
> 2.20.1.791.gb4d0f1c61a-goog
>