new file mode 100644
@@ -0,0 +1,30 @@
+Required properties:
+- compatible: Should be "st,st33htpm-i2c" or "tcg,tpm_tis-i2c"
+- clock-frequency: I²C work frequency.
+- reg: address on the bus
+
+Optional TPM_TIS Properties:
+- interrupt-parent: phandle for the interrupt gpio controller
+- interrupts: GPIO interrupt to which the chip is connected
+
+Optional SoC Specific Properties:
+- pinctrl-names: Contains only one value - "default".
+- pintctrl-0: Specifies the pin control groups used for this controller.
+
+Example (for ARM-based BeagleBoard xM with TPM_TIS on I2C2):
+
+&i2c2 {
+
+ status = "okay";
+
+ tpm_tis: tpm_tis@17 {
+
+ compatible = "st,tpm_tis-i2c";
+
+ reg = <0x17>;
+ clock-frequency = <400000>;
+
+ interrupt-parent = <&gpio5>;
+ interrupts = <7 IRQ_TYPE_LEVEL_HIGH>;
+ };
+};
@@ -72,7 +72,19 @@ config TCG_TIS_SPI
TCG TIS 1.3 TPM specification (TPM1.2) or the TCG PTP FIFO
specification (TPM2.0) say Yes and it will be accessible from
within Linux. To compile this driver as a module, choose M here;
- the module will be called tpm_spi_tis.
+ the module will be called tpm_tis_spi.
+
+config TCG_TIS_I2C
+ tristate "TPM Interface Specification 1.3 Interface / TPM 2.0 FIFO Interface - (I2C)"
+ depends on I2C
+ depends on CRC_CCITT
+ ---help---
+ If you have a TPM security chip which is connected to a regular,
+ non-tcg I2C master (i.e. most embedded platforms) that is compliant with the
+ TCG TIS 1.3 TPM specification (TPM1.2) or the TCG PTP FIFO
+ specification (TPM2.0) say Yes and it will be accessible from
+ within Linux. To compile this driver as a module, choose M here;
+ the module will be called tpm_tis_i2c.
config TCG_NSC
tristate "National Semiconductor TPM Interface"
@@ -17,6 +17,7 @@ obj-$(CONFIG_TCG_TIS_I2C_ATMEL) += tpm_i2c_atmel.o
obj-$(CONFIG_TCG_TIS_I2C_INFINEON) += tpm_i2c_infineon.o
obj-$(CONFIG_TCG_TIS_I2C_NUVOTON) += tpm_i2c_nuvoton.o
obj-$(CONFIG_TCG_TIS_SPI) += tpm_tis_spi.o
+obj-$(CONFIG_TCG_TIS_I2C) += tpm_tis_i2c.o
obj-$(CONFIG_TCG_NSC) += tpm_nsc.o
obj-$(CONFIG_TCG_ATMEL) += tpm_atmel.o
obj-$(CONFIG_TCG_INFINEON) += tpm_infineon.o
new file mode 100644
@@ -0,0 +1,499 @@
+/*
+ * STMicroelectronics TPM I2C Linux driver for TPM ST33ZP24
+ * Copyright (C) 2016 STMicroelectronics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/wait.h>
+#include <linux/acpi.h>
+#include <linux/freezer.h>
+#include <linux/crc-ccitt.h>
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/of_irq.h>
+#include <linux/of_gpio.h>
+#include <linux/tpm.h>
+#include "tpm.h"
+#include "tpm_tis_core.h"
+
+#define TPM_LOC_SEL 0x04
+#define TPM_I2C_INTERFACE_CAPABILITY 0x30
+#define TPM_I2C_DEVICE_ADDRESS 0x38
+#define TPM_DATA_CSUM_ENABLE 0x40
+#define TPM_DATA_CSUM 0x44
+#define TPM_I2C_DID_VID 0x48
+#define TPM_I2C_RID 0x4C
+
+#define TPM_I2C_DEFAULT_GUARD_TIME 0xFA
+
+enum tpm_tis_i2c_operation {
+ TPM_I2C_NONE,
+ TPM_I2C_RECV,
+ TPM_I2C_SEND,
+};
+
+#define TPM_I2C_DEVADRCHANGE(x) ((0x18000000 & x) >> 27)
+#define TPM_I2C_READ_READ(x) ((0x00100000 & x) >> 20)
+#define TPM_I2C_READ_WRITE(x) ((0x00080000 & x) >> 19)
+#define TPM_I2C_WRITE_READ(x) ((0x00040000 & x) >> 18)
+#define TPM_I2C_WRITE_WRITE(x) ((0x00020000 & x) >> 17)
+#define TPM_I2C_GUARD_TIME(x) ((0x0001FE00 & x) >> 9)
+
+struct tpm_tis_i2c_phy {
+ struct i2c_client *client;
+ u8 buf[TPM_BUFSIZE + 1];
+ u8 last_i2c_ops;
+
+ struct timer_list guard_timer;
+ struct mutex phy_lock;
+
+ bool data_csum;
+ bool devadrchange;
+ bool read_read;
+ bool read_write;
+ bool write_read;
+ bool write_write;
+ u8 guard_time;
+};
+
+static int tpm_tis_i2c_ptp_register_mapper(u32 addr, u8 *i2c_reg)
+{
+ *i2c_reg = (u8)(0x000000ff & addr);
+
+ switch (addr) {
+ case TPM_ACCESS(0):
+ *i2c_reg = TPM_LOC_SEL;
+ break;
+ case TPM_LOC_SEL:
+ *i2c_reg = TPM_ACCESS(0);
+ break;
+ case TPM_DID_VID(0):
+ *i2c_reg = TPM_I2C_DID_VID;
+ break;
+ case TPM_RID(0):
+ *i2c_reg = TPM_I2C_RID;
+ break;
+ case TPM_INT_VECTOR(0):
+ return -1;
+ }
+
+ return 0;
+}
+
+static void tpm_tis_i2c_guard_time_timeout(unsigned long data)
+{
+ struct tpm_tis_i2c_phy *phy = (struct tpm_tis_i2c_phy *)data;
+
+ pr_debug("\n");
+
+ /* GUARD_TIME expired */
+ phy->last_i2c_ops = TPM_I2C_NONE;
+}
+
+static void tpm_tis_i2c_sleep_guard_time(struct tpm_tis_i2c_phy *phy,
+ u8 i2c_operation)
+{
+ del_timer_sync(&phy->guard_timer);
+ switch (i2c_operation) {
+ case TPM_I2C_RECV:
+ switch (phy->last_i2c_ops) {
+ case TPM_I2C_RECV:
+ if (phy->read_read)
+ udelay(phy->guard_time);
+ break;
+ case TPM_I2C_SEND:
+ if (phy->write_read)
+ udelay(phy->guard_time);
+ break;
+ }
+ break;
+ case TPM_I2C_SEND:
+ switch (phy->last_i2c_ops) {
+ case TPM_I2C_RECV:
+ if (phy->read_write)
+ udelay(phy->guard_time);
+ break;
+ case TPM_I2C_SEND:
+ if (phy->write_write)
+ udelay(phy->guard_time);
+ break;
+ }
+ break;
+ }
+ phy->last_i2c_ops = i2c_operation;
+}
+
+static int tpm_tis_i2c_read_bytes(struct tpm_chip *chip, u32 addr, size_t len,
+ u8 size, u8 *result)
+{
+ struct tpm_tis_i2c_phy *phy = tpm_get_vendordata(chip);
+ int i, ret;
+ u8 i2c_reg;
+
+ mutex_lock(&phy->phy_lock);
+ ret = tpm_tis_i2c_ptp_register_mapper(addr, &i2c_reg);
+ if (ret < 0) {
+ /* If we don't have any register equivalence in i2c
+ * ignore the sequence.
+ */
+ ret = len;
+ goto exit;
+ }
+ ret = -1;
+
+ for (i = 0; i < TPM_RETRY && ret < 0; i++) {
+ tpm_tis_i2c_sleep_guard_time(phy, TPM_I2C_SEND);
+ ret = i2c_master_send(phy->client, &i2c_reg, 1);
+ mod_timer(&phy->guard_timer, phy->guard_time);
+ }
+
+ if (ret < 0)
+ goto exit;
+
+ ret = -1;
+ for (i = 0; i < TPM_RETRY && ret < 0; i++) {
+ tpm_tis_i2c_sleep_guard_time(phy, TPM_I2C_RECV);
+ ret = i2c_master_recv(phy->client, result, len * size);
+ mod_timer(&phy->guard_timer, phy->guard_time);
+ }
+
+exit:
+ mutex_unlock(&phy->phy_lock);
+ return ret;
+}
+
+static int tpm_tis_i2c_write_bytes(struct tpm_chip *chip, u32 addr, size_t len,
+ u8 size, u8 *value)
+{
+ struct tpm_tis_i2c_phy *phy = tpm_get_vendordata(chip);
+ int i, ret;
+ u8 i2c_reg;
+
+ mutex_lock(&phy->phy_lock);
+ ret = tpm_tis_i2c_ptp_register_mapper(addr, &i2c_reg);
+ if (ret < 0) {
+ /* If we don't have any register equivalence in i2c
+ * ignore the sequence.
+ */
+ ret = len * size;
+ goto exit;
+ }
+
+ ret = -1;
+ phy->buf[0] = i2c_reg;
+ memcpy(phy->buf + 1, value, len * size);
+
+ for (i = 0; i < TPM_RETRY && (ret < 0 || ret < len + 1); i++) {
+ tpm_tis_i2c_sleep_guard_time(phy, TPM_I2C_SEND);
+ ret = i2c_master_send(phy->client, phy->buf, len * size + 1);
+ mod_timer(&phy->guard_timer, phy->guard_time);
+ }
+
+exit:
+ mutex_unlock(&phy->phy_lock);
+ return ret;
+}
+
+static bool tpm_tis_i2c_check_data(struct tpm_chip *chip, u8 *buf, size_t len)
+{
+ struct tpm_tis_i2c_phy *phy = tpm_get_vendordata(chip);
+ u16 crc, crc_tpm;
+
+ if (phy->data_csum) {
+ crc = crc_ccitt(0x0000, buf, len);
+
+ crc_tpm = tpm_read_word(chip, TPM_DATA_CSUM);
+ crc_tpm = be16_to_cpu(crc_tpm);
+
+ return crc == crc_tpm;
+ }
+ return true;
+}
+
+static const struct tpm_class_ops tpm_tis = {
+ .status = tpm_tis_status,
+ .recv = tpm_tis_recv,
+ .send = tpm_tis_send,
+ .cancel = tpm_tis_ready,
+ .req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID,
+ .req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID,
+ .req_canceled = tpm_tis_req_canceled,
+ .read_bytes = tpm_tis_i2c_read_bytes,
+ .write_bytes = tpm_tis_i2c_write_bytes,
+ .check_data = tpm_tis_i2c_check_data,
+};
+
+static SIMPLE_DEV_PM_OPS(tpm_tis_i2c_pm, tpm_pm_suspend, tpm_tis_resume);
+
+static ssize_t i2c_addr_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ if (client)
+ return sprintf(buf, "0x%.2x\n", client->addr);
+
+ return 0;
+}
+
+static ssize_t i2c_addr_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct tpm_chip *chip = dev_get_drvdata(dev);
+ struct tpm_tis_i2c_phy *phy;
+ long new_addr;
+ u16 cur_addr;
+ int ret = 0;
+
+ if (!chip)
+ goto exit;
+
+ phy = tpm_get_vendordata(chip);
+ if (!phy || !phy->client || !phy->devadrchange)
+ goto exit;
+
+ /* Base string automatically detected */
+ ret = kstrtol(buf, 0, &new_addr);
+ if (ret < 0)
+ goto exit;
+
+ ret = tpm_write_word(chip, TPM_I2C_DEVICE_ADDRESS, new_addr);
+ if (ret < 0)
+ goto exit;
+
+ cur_addr = tpm_read_word(chip, TPM_I2C_DEVICE_ADDRESS);
+ if (cur_addr == new_addr) {
+ phy->client->addr = new_addr & 0x00ff;
+ return count;
+ }
+
+ return -EINVAL;
+exit:
+ return ret;
+}
+static DEVICE_ATTR_RW(i2c_addr);
+
+static ssize_t csum_state_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct tpm_chip *chip = dev_get_drvdata(dev);
+ struct tpm_tis_i2c_phy *phy;
+
+ if (!chip)
+ goto exit;
+
+ phy = tpm_get_vendordata(chip);
+ if (!phy || !phy->client)
+ goto exit;
+
+ return sprintf(buf, "%x\n", phy->data_csum);
+exit:
+ return 0;
+}
+
+static ssize_t csum_state_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct tpm_chip *chip = dev_get_drvdata(dev);
+ struct tpm_tis_i2c_phy *phy;
+ long new_state;
+ u8 cur_state;
+ int ret = 0;
+
+ if (!chip)
+ goto exit;
+
+ phy = tpm_get_vendordata(chip);
+ if (!phy || !phy->client)
+ goto exit;
+
+ ret = kstrtol(buf, 2, &new_state);
+ if (ret < 0)
+ goto exit;
+
+ ret = tpm_write_byte(chip, TPM_DATA_CSUM_ENABLE, new_state);
+ if (ret < 0)
+ goto exit;
+
+ cur_state = tpm_read_byte(chip, TPM_DATA_CSUM_ENABLE);
+ if (new_state == cur_state) {
+ phy->data_csum = cur_state;
+ return count;
+ }
+
+ return -EINVAL;
+exit:
+ return ret;
+}
+static DEVICE_ATTR_RW(csum_state);
+
+static struct attribute *tpm_tis_i2c_attrs[] = {
+ &dev_attr_i2c_addr.attr,
+ &dev_attr_csum_state.attr,
+ NULL,
+};
+
+static struct attribute_group tpm_tis_i2c_attr_group = {
+ .attrs = tpm_tis_i2c_attrs,
+};
+
+static int tpm_tis_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct tpm_tis_i2c_phy *phy;
+ struct tpm_chip *chip;
+ unsigned int irq_polarity = IRQ_TYPE_NONE;
+ int ret, irq = -1;
+ u32 tmp;
+
+ if (!client) {
+ pr_err("%s: i2c client is NULL. Device not accessible.\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_info(&client->dev, "client not i2c capable\n");
+ return -ENODEV;
+ }
+
+ phy = devm_kzalloc(&client->dev, sizeof(struct tpm_tis_i2c_phy),
+ GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ chip = tpmm_chip_alloc(&client->dev, &tpm_tis);
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+
+ phy->client = client;
+ mutex_init(&phy->phy_lock);
+ tpm_set_vendordata(chip, phy);
+ i2c_set_clientdata(client, chip);
+
+ phy->guard_time = TPM_I2C_DEFAULT_GUARD_TIME;
+ phy->read_read = true;
+ phy->read_write = true;
+ phy->write_read = true;
+ phy->write_write = true;
+
+ /* initialize timer */
+ init_timer(&phy->guard_timer);
+ phy->guard_timer.data = (unsigned long)phy;
+ phy->guard_timer.function = tpm_tis_i2c_guard_time_timeout;
+
+ ret = tpm_write_byte(chip, TPM_LOC_SEL, 0);
+ if (ret < 0)
+ goto out_err;
+
+ phy->data_csum = tpm_read_byte(chip, TPM_DATA_CSUM_ENABLE);
+
+ if (client->irq > 0) {
+ irq = client->irq;
+ irq_polarity = irq_get_trigger_type(irq);
+ if (irq_polarity > 0)
+ irq_polarity |= IRQF_ONESHOT;
+ else
+ irq = -1;
+ }
+
+ ret = tpm_tis_init_core(&client->dev, chip, irq, irq_polarity);
+ if (ret < 0)
+ goto out_err;
+
+ tmp = tpm_read_dword(chip, TPM_I2C_INTERFACE_CAPABILITY);
+
+ tmp = be32_to_cpu((__force __be32)tmp);
+
+ phy->devadrchange = TPM_I2C_DEVADRCHANGE(tmp);
+ phy->read_read = TPM_I2C_READ_READ(tmp);
+ phy->read_write = TPM_I2C_READ_WRITE(tmp);
+ phy->write_read = TPM_I2C_WRITE_READ(tmp);
+ phy->write_write = TPM_I2C_WRITE_WRITE(tmp);
+ phy->guard_time = TPM_I2C_GUARD_TIME(tmp);
+
+ ret = sysfs_create_group(&client->dev.kobj, &tpm_tis_i2c_attr_group);
+ if (ret < 0) {
+ dev_err(&chip->dev,
+ "failed to create sysfs attributes, %d\n", ret);
+ goto deinit_err;
+ }
+
+ return ret;
+
+deinit_err:
+ tpm_chip_unregister(chip);
+out_err:
+ tpm_tis_remove(chip);
+ return ret;
+}
+
+static int tpm_tis_i2c_remove(struct i2c_client *client)
+{
+ struct tpm_chip *chip = i2c_get_clientdata(client);
+
+ tpm_chip_unregister(chip);
+ tpm_tis_remove(chip);
+ sysfs_remove_group(&client->dev.kobj, &tpm_tis_i2c_attr_group);
+ return 0;
+}
+
+static const struct i2c_device_id tpm_tis_i2c_id[] = {
+ {"tpm_tis_i2c", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, tpm_tis_i2c_id);
+
+static const struct of_device_id of_tis_i2c_match[] = {
+ { .compatible = "st,st33htpm-i2c", },
+ { .compatible = "tcg,tpm_tis-i2c", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, of_tis_i2c_match);
+
+static const struct acpi_device_id acpi_tis_i2c_match[] = {
+ {"SMO0768", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, acpi_tis_i2c_match);
+
+static struct i2c_driver tpm_tis_i2c_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "tpm_tis_i2c",
+ .pm = &tpm_tis_i2c_pm,
+ .of_match_table = of_match_ptr(of_tis_i2c_match),
+ .acpi_match_table = ACPI_PTR(acpi_tis_i2c_match),
+ },
+ .probe = tpm_tis_i2c_probe,
+ .remove = tpm_tis_i2c_remove,
+ .id_table = tpm_tis_i2c_id,
+};
+
+module_i2c_driver(tpm_tis_i2c_driver);
+
+MODULE_DESCRIPTION("TPM Driver for native I2C access");
+MODULE_LICENSE("GPL");
A i2c protocol standardized by the TCG is now supported by most of TPM vendors. Signed-off-by: Christophe Ricard <christophe-h.ricard@st.com> --- .../bindings/security/tpm/tpm_tis_i2c.txt | 30 ++ drivers/char/tpm/Kconfig | 14 +- drivers/char/tpm/Makefile | 1 + drivers/char/tpm/tpm_tis_i2c.c | 499 +++++++++++++++++++++ 4 files changed, 543 insertions(+), 1 deletion(-) create mode 100644 Documentation/devicetree/bindings/security/tpm/tpm_tis_i2c.txt create mode 100644 drivers/char/tpm/tpm_tis_i2c.c