aboutsummaryrefslogtreecommitdiff
path: root/src/ec/kontron/kempld/kempld_i2c.c
diff options
context:
space:
mode:
authorNico Huber <nico.huber@secunet.com>2017-09-19 14:13:34 +0200
committerPatrick Georgi <pgeorgi@google.com>2019-01-24 13:56:25 +0000
commit016ef9e9bc36c60101b707a8c1c926e78350befa (patch)
tree8c9254f2d4888d7bf14e18f2562f2fde564f724e /src/ec/kontron/kempld/kempld_i2c.c
parentb2e610011cfa8c821d93b474274fdab383462c49 (diff)
ec/kontron: Add support for Kontron kempld
A programmable logic device used by Kontron as EC on their COM express modules. The name `kempld` is taken from Linux kernel sources, as is the I2C driver. The meaning of the acronym is unclear, probably: Kontron Embedded Module PLD. Change-Id: If9a0826c4a8f5c8cd573610c2d10561334258b36 Signed-off-by: Nico Huber <nico.huber@secunet.com> Reviewed-on: https://review.coreboot.org/c/29476 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
Diffstat (limited to 'src/ec/kontron/kempld/kempld_i2c.c')
-rw-r--r--src/ec/kontron/kempld/kempld_i2c.c296
1 files changed, 296 insertions, 0 deletions
diff --git a/src/ec/kontron/kempld/kempld_i2c.c b/src/ec/kontron/kempld/kempld_i2c.c
new file mode 100644
index 0000000000..fdc0b50dfc
--- /dev/null
+++ b/src/ec/kontron/kempld/kempld_i2c.c
@@ -0,0 +1,296 @@
+/*
+ * I2C bus driver for Kontron COM modules
+ *
+ * Copyright (C) 2017 secunet Security Networks AG
+ *
+ * Based on the similar driver in Linux:
+ *
+ * Copyright (c) 2010-2013 Kontron Europe GmbH
+ * Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ * The driver is based on the i2c-ocores driver by Peter Korsgaard.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 2 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdint.h>
+#include <console/console.h>
+#include <device/device.h>
+#include <device/i2c_bus.h>
+#include <timer.h>
+#include <delay.h>
+
+#include "kempld.h"
+#include "kempld_internal.h"
+
+#define KEMPLD_I2C_PRELOW 0x0b
+#define KEMPLD_I2C_PREHIGH 0x0c
+#define KEMPLD_I2C_DATA 0x0e
+
+#define KEMPLD_I2C_CTRL 0x0d
+#define I2C_CTRL_IEN 0x40
+#define I2C_CTRL_EN 0x80
+
+#define KEMPLD_I2C_STAT 0x0f
+#define I2C_STAT_IF 0x01
+#define I2C_STAT_TIP 0x02
+#define I2C_STAT_ARBLOST 0x20
+#define I2C_STAT_BUSY 0x40
+#define I2C_STAT_NACK 0x80
+
+#define KEMPLD_I2C_CMD 0x0f
+#define I2C_CMD_START 0x91
+#define I2C_CMD_STOP 0x41
+#define I2C_CMD_READ 0x21
+#define I2C_CMD_WRITE 0x11
+#define I2C_CMD_READ_ACK 0x21
+#define I2C_CMD_READ_NACK 0x29
+#define I2C_CMD_IACK 0x01
+
+#define KEMPLD_I2C_FREQ_MAX 2700 /* 2.7 mHz */
+#define KEMPLD_I2C_FREQ_STD 100 /* 100 kHz */
+
+#define EIO 5
+#define ENXIO 6
+#define EAGAIN 11
+#define EBUSY 16
+#define ETIMEDOUT 110
+
+enum kempld_i2c_state {
+ STATE_DONE = 0,
+ STATE_INIT,
+ STATE_ADDR,
+ STATE_ADDR10,
+ STATE_START,
+ STATE_WRITE,
+ STATE_READ,
+ STATE_ERROR,
+};
+
+struct kempld_i2c_data {
+ const struct i2c_msg *msg;
+ size_t pos;
+ size_t nmsgs;
+ enum kempld_i2c_state state;
+};
+
+/*
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+static int kempld_i2c_process(struct kempld_i2c_data *const i2c)
+{
+ u8 stat = kempld_read8(KEMPLD_I2C_STAT);
+ const struct i2c_msg *msg = i2c->msg;
+ u8 addr;
+
+ /* Ready? */
+ if (stat & I2C_STAT_TIP)
+ return -EBUSY;
+
+ if (i2c->state == STATE_DONE || i2c->state == STATE_ERROR) {
+ /* Stop has been sent */
+ kempld_write8(KEMPLD_I2C_CMD, I2C_CMD_IACK);
+ if (i2c->state == STATE_ERROR)
+ return -EIO;
+ return 0;
+ }
+
+ /* Error? */
+ if (stat & I2C_STAT_ARBLOST) {
+ i2c->state = STATE_ERROR;
+ kempld_write8(KEMPLD_I2C_CMD, I2C_CMD_STOP);
+ return -EAGAIN;
+ }
+
+ if (i2c->state == STATE_INIT) {
+ if (stat & I2C_STAT_BUSY)
+ return -EBUSY;
+
+ i2c->state = STATE_ADDR;
+ }
+
+ if (i2c->state == STATE_ADDR) {
+ /* 10 bit address? */
+ if (i2c->msg->flags & I2C_M_TEN) {
+ addr = 0xf0 | ((i2c->msg->slave >> 7) & 0x6);
+ i2c->state = STATE_ADDR10;
+ } else {
+ addr = (i2c->msg->slave << 1);
+ i2c->state = STATE_START;
+ }
+
+ /* Set read bit if necessary */
+ addr |= (i2c->msg->flags & I2C_M_RD) ? 1 : 0;
+
+ kempld_write8(KEMPLD_I2C_DATA, addr);
+ kempld_write8(KEMPLD_I2C_CMD, I2C_CMD_START);
+
+ return 0;
+ }
+
+ /* Second part of 10 bit addressing */
+ if (i2c->state == STATE_ADDR10) {
+ kempld_write8(KEMPLD_I2C_DATA, i2c->msg->slave & 0xff);
+ kempld_write8(KEMPLD_I2C_CMD, I2C_CMD_WRITE);
+
+ i2c->state = STATE_START;
+ return 0;
+ }
+
+ if (i2c->state == STATE_START || i2c->state == STATE_WRITE) {
+ i2c->state = (msg->flags & I2C_M_RD) ? STATE_READ : STATE_WRITE;
+
+ if (stat & I2C_STAT_NACK) {
+ i2c->state = STATE_ERROR;
+ kempld_write8(KEMPLD_I2C_CMD, I2C_CMD_STOP);
+ return -ENXIO;
+ }
+ } else {
+ msg->buf[i2c->pos++] = kempld_read8(KEMPLD_I2C_DATA);
+ }
+
+ if (i2c->pos >= msg->len) {
+ i2c->nmsgs--;
+ i2c->msg++;
+ i2c->pos = 0;
+ msg = i2c->msg;
+
+ if (i2c->nmsgs) {
+ if (!(msg->flags & I2C_M_NOSTART)) {
+ i2c->state = STATE_ADDR;
+ return 0;
+ } else {
+ i2c->state = (msg->flags & I2C_M_RD)
+ ? STATE_READ : STATE_WRITE;
+ }
+ } else {
+ i2c->state = STATE_DONE;
+ kempld_write8(KEMPLD_I2C_CMD, I2C_CMD_STOP);
+ return 0;
+ }
+ }
+
+ if (i2c->state == STATE_READ) {
+ kempld_write8(KEMPLD_I2C_CMD, i2c->pos == (msg->len - 1) ?
+ I2C_CMD_READ_NACK : I2C_CMD_READ_ACK);
+ } else {
+ kempld_write8(KEMPLD_I2C_DATA, msg->buf[i2c->pos++]);
+ kempld_write8(KEMPLD_I2C_CMD, I2C_CMD_WRITE);
+ }
+
+ return 0;
+}
+
+static int kempld_i2c_xfer(struct device *const dev,
+ const struct i2c_msg *const msgs,
+ const size_t num)
+{
+ struct kempld_i2c_data i2c;
+ struct stopwatch sw;
+ int ret;
+
+ if (kempld_get_mutex(100) < 0)
+ return -ENXIO;
+
+ i2c.msg = msgs;
+ i2c.pos = 0;
+ i2c.nmsgs = num;
+ i2c.state = STATE_INIT;
+
+ /* Handle the transfer */
+ stopwatch_init_msecs_expire(&sw, 1000);
+ while (!stopwatch_expired(&sw)) {
+ ret = kempld_i2c_process(&i2c);
+
+ if (i2c.state == STATE_DONE || i2c.state == STATE_ERROR) {
+ if (i2c.state == STATE_DONE) {
+ printk(BIOS_SPEW, "kempld_i2c: Processed %zu segments.\n", num);
+ ret = 0;
+ } else {
+ printk(BIOS_INFO, "kempld_i2c: Transfer failed.\n");
+ }
+ goto _release;
+ }
+
+ if (ret == 0)
+ stopwatch_init_msecs_expire(&sw, 1000);
+
+ udelay(10);
+ }
+
+ i2c.state = STATE_ERROR;
+ ret = -ETIMEDOUT;
+ printk(BIOS_INFO, "kempld_i2c: Transfer failed.\n");
+
+_release:
+ kempld_release_mutex();
+ return ret;
+}
+
+static const struct i2c_bus_operations kempld_i2c_bus_ops = {
+ .transfer = kempld_i2c_xfer,
+};
+
+static struct device_operations kempld_i2c_dev_ops = {
+ .scan_bus = &scan_smbus,
+ .ops_i2c_bus = &kempld_i2c_bus_ops,
+};
+
+void kempld_i2c_device_init(struct device *const dev)
+{
+ u16 prescale_corr;
+ long prescale;
+ u8 ctrl;
+ u8 stat;
+ u8 cfg;
+
+ if (kempld_get_mutex(100) < 0)
+ return;
+
+ /* Make sure the device is disabled */
+ ctrl = kempld_read8(KEMPLD_I2C_CTRL);
+ ctrl &= ~(I2C_CTRL_EN | I2C_CTRL_IEN);
+ kempld_write8(KEMPLD_I2C_CTRL, ctrl);
+
+ const u8 spec_major = KEMPLD_SPEC_GET_MAJOR(kempld_read8(KEMPLD_SPEC));
+ if (spec_major == 1)
+ prescale = KEMPLD_CLK / (KEMPLD_I2C_FREQ_STD * 5) - 1000;
+ else
+ prescale = KEMPLD_CLK / (KEMPLD_I2C_FREQ_STD * 4) - 3000;
+
+ if (prescale < 0)
+ prescale = 0;
+
+ /* Round to the best matching value */
+ prescale_corr = prescale / 1000;
+ if (prescale % 1000 >= 500)
+ prescale_corr++;
+
+ kempld_write8(KEMPLD_I2C_PRELOW, prescale_corr & 0xff);
+ kempld_write8(KEMPLD_I2C_PREHIGH, prescale_corr >> 8);
+
+ /* Disable I2C bus output on GPIO pins */
+ cfg = kempld_read8(KEMPLD_CFG);
+ cfg &= ~KEMPLD_CFG_GPIO_I2C_MUX;
+ kempld_write8(KEMPLD_CFG, cfg);
+
+ /* Enable the device */
+ kempld_write8(KEMPLD_I2C_CMD, I2C_CMD_IACK);
+ ctrl |= I2C_CTRL_EN;
+ kempld_write8(KEMPLD_I2C_CTRL, ctrl);
+
+ stat = kempld_read8(KEMPLD_I2C_STAT);
+ if (stat & I2C_STAT_BUSY)
+ kempld_write8(KEMPLD_I2C_CMD, I2C_CMD_STOP);
+
+ dev->ops = &kempld_i2c_dev_ops;
+
+ kempld_release_mutex();
+}