/* * STMicroelectronics TPM I2C Linux driver for TPM ST33ZP24 * Copyright (C) 2009, 2010 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * STMicroelectronics version 1.2.0, Copyright (C) 2010 * STMicroelectronics comes with ABSOLUTELY NO WARRANTY. * This is free software, and you are welcome to redistribute it * under certain conditions. * * @Author: Christophe RICARD tpmsupport@st.com * * @File: tpm_stm_st33_i2c.c * * @Synopsis: * 09/15/2010: First shot driver tpm_tis driver for lpc is used as model. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tpm.h" #include "tpm_i2c_stm_st33.h" enum stm33zp24_access { TPM_ACCESS_VALID = 0x80, TPM_ACCESS_ACTIVE_LOCALITY = 0x20, TPM_ACCESS_REQUEST_PENDING = 0x04, TPM_ACCESS_REQUEST_USE = 0x02, }; enum stm33zp24_status { TPM_STS_VALID = 0x80, TPM_STS_COMMAND_READY = 0x40, TPM_STS_GO = 0x20, TPM_STS_DATA_AVAIL = 0x10, TPM_STS_DATA_EXPECT = 0x08, }; enum stm33zp24_int_flags { TPM_GLOBAL_INT_ENABLE = 0x80, TPM_INTF_CMD_READY_INT = 0x080, TPM_INTF_FIFO_AVALAIBLE_INT = 0x040, TPM_INTF_WAKE_UP_READY_INT = 0x020, TPM_INTF_LOCALITY_CHANGE_INT = 0x004, TPM_INTF_STS_VALID_INT = 0x002, TPM_INTF_DATA_AVAIL_INT = 0x001, }; enum tis_defaults { TIS_SHORT_TIMEOUT = 750, TIS_LONG_TIMEOUT = 2000, }; /* * write8_reg * Send byte to the TIS register according to the ST33ZP24 I2C protocol. * @param: tpm_register, the tpm tis register where the data should be written * @param: tpm_data, the tpm_data to write inside the tpm_register * @param: tpm_size, The length of the data * @return: Returns negative errno, or else the number of bytes written. */ static int write8_reg(struct i2c_client *client, u8 tpm_register, u8 *tpm_data, u16 tpm_size) { struct st33zp24_platform_data *pin_infos; pin_infos = client->dev.platform_data; pin_infos->tpm_i2c_buffer[0][0] = tpm_register; memcpy(&pin_infos->tpm_i2c_buffer[0][1], tpm_data, tpm_size); return i2c_master_send(client, pin_infos->tpm_i2c_buffer[0], tpm_size + 1); } /* write8_reg() */ /* * read8_reg * Recv byte from the TIS register according to the ST33ZP24 I2C protocol. * @param: tpm_register, the tpm tis register where the data should be read * @param: tpm_data, the TPM response * @param: tpm_size, tpm TPM response size to read. * @return: number of byte read successfully: should be one if success. */ static int read8_reg(struct i2c_client *client, u8 tpm_register, u8 *tpm_data, int tpm_size) { u8 status = 0; u8 data; data = TPM_DUMMY_BYTE; status = write8_reg(client, tpm_register, &data, 1); if (status == 2) status = i2c_master_recv(client, tpm_data, tpm_size); return status; } /* read8_reg() */ /* * I2C_WRITE_DATA * Send byte to the TIS register according to the ST33ZP24 I2C protocol. * @param: client, the chip description * @param: tpm_register, the tpm tis register where the data should be written * @param: tpm_data, the tpm_data to write inside the tpm_register * @param: tpm_size, The length of the data * @return: number of byte written successfully: should be one if success. */ #define I2C_WRITE_DATA(client, tpm_register, tpm_data, tpm_size) \ (write8_reg(client, tpm_register | \ TPM_WRITE_DIRECTION, tpm_data, tpm_size)) /* * I2C_READ_DATA * Recv byte from the TIS register according to the ST33ZP24 I2C protocol. * @param: tpm, the chip description * @param: tpm_register, the tpm tis register where the data should be read * @param: tpm_data, the TPM response * @param: tpm_size, tpm TPM response size to read. * @return: number of byte read successfully: should be one if success. */ #define I2C_READ_DATA(client, tpm_register, tpm_data, tpm_size) \ (read8_reg(client, tpm_register, tpm_data, tpm_size)) /* * clear_interruption * clear the TPM interrupt register. * @param: tpm, the chip description */ static void clear_interruption(struct i2c_client *client) { u8 interrupt; I2C_READ_DATA(client, TPM_INT_STATUS, &interrupt, 1); I2C_WRITE_DATA(client, TPM_INT_STATUS, &interrupt, 1); I2C_READ_DATA(client, TPM_INT_STATUS, &interrupt, 1); } /* clear_interruption() */ /* * _wait_for_interrupt_serirq_timeout * @param: tpm, the chip description * @param: timeout, the timeout of the interrupt * @return: the status of the interruption. */ static long _wait_for_interrupt_serirq_timeout(struct tpm_chip *chip, unsigned long timeout) { long status; struct i2c_client *client; struct st33zp24_platform_data *pin_infos; client = (struct i2c_client *)TPM_VPRIV(chip); pin_infos = client->dev.platform_data; status = wait_for_completion_interruptible_timeout( &pin_infos->irq_detection, timeout); if (status > 0) enable_irq(gpio_to_irq(pin_infos->io_serirq)); gpio_direction_input(pin_infos->io_serirq); return status; } /* wait_for_interrupt_serirq_timeout() */ static int wait_for_serirq_timeout(struct tpm_chip *chip, bool condition, unsigned long timeout) { int status = 2; struct i2c_client *client; client = (struct i2c_client *)TPM_VPRIV(chip); status = _wait_for_interrupt_serirq_timeout(chip, timeout); if (!status) { status = -EBUSY; } else { clear_interruption(client); if (condition) status = 1; } return status; } /* * tpm_stm_i2c_cancel, cancel is not implemented. * @param: chip, the tpm_chip description as specified in driver/char/tpm/tpm.h */ static void tpm_stm_i2c_cancel(struct tpm_chip *chip) { struct i2c_client *client; u8 data; client = (struct i2c_client *)TPM_VPRIV(chip); data = TPM_STS_COMMAND_READY; I2C_WRITE_DATA(client, TPM_STS, &data, 1); if (chip->vendor.irq) wait_for_serirq_timeout(chip, 1, chip->vendor.timeout_a); } /* tpm_stm_i2c_cancel() */ /* * tpm_stm_spi_status return the TPM_STS register * @param: chip, the tpm chip description * @return: the TPM_STS register value. */ static u8 tpm_stm_i2c_status(struct tpm_chip *chip) { struct i2c_client *client; u8 data; client = (struct i2c_client *)TPM_VPRIV(chip); I2C_READ_DATA(client, TPM_STS, &data, 1); return data; } /* tpm_stm_i2c_status() */ /* * check_locality if the locality is active * @param: chip, the tpm chip description * @return: the active locality or -EACCESS. */ static int check_locality(struct tpm_chip *chip) { struct i2c_client *client; u8 data; u8 status; client = (struct i2c_client *)TPM_VPRIV(chip); status = I2C_READ_DATA(client, TPM_ACCESS, &data, 1); if (status && (data & (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) == (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) return chip->vendor.locality; return -EACCES; } /* check_locality() */ /* * request_locality request the TPM locality * @param: chip, the chip description * @return: the active locality or EACCESS. */ static int request_locality(struct tpm_chip *chip) { unsigned long stop; long rc; struct i2c_client *client; u8 data; client = (struct i2c_client *)TPM_VPRIV(chip); if (check_locality(chip) == chip->vendor.locality) return chip->vendor.locality; data = TPM_ACCESS_REQUEST_USE; rc = I2C_WRITE_DATA(client, TPM_ACCESS, &data, 1); if (rc < 0) goto end; if (chip->vendor.irq) { rc = wait_for_serirq_timeout(chip, (check_locality (chip) >= 0), chip->vendor.timeout_a); if (rc > 0) return chip->vendor.locality; } else { stop = jiffies + chip->vendor.timeout_a; do { if (check_locality(chip) >= 0) return chip->vendor.locality; msleep(TPM_TIMEOUT); } while (time_before(jiffies, stop)); } rc = -EACCES; end: return rc; } /* request_locality() */ /* * release_locality release the active locality * @param: chip, the tpm chip description. */ static void release_locality(struct tpm_chip *chip) { struct i2c_client *client; u8 data; client = (struct i2c_client *)TPM_VPRIV(chip); data = TPM_ACCESS_ACTIVE_LOCALITY; I2C_WRITE_DATA(client, TPM_ACCESS, &data, 1); } /* * get_burstcount return the burstcount address 0x19 0x1A * @param: chip, the chip description * return: the burstcount. */ static int get_burstcount(struct tpm_chip *chip) { unsigned long stop; int burstcnt, status; u8 tpm_reg, temp; struct i2c_client *client = (struct i2c_client *)TPM_VPRIV(chip); stop = jiffies + chip->vendor.timeout_d; do { tpm_reg = TPM_STS + 1; status = I2C_READ_DATA(client, tpm_reg, &temp, 1); if (status < 0) goto end; tpm_reg = tpm_reg + 1; burstcnt = temp; status = I2C_READ_DATA(client, tpm_reg, &temp, 1); if (status < 0) goto end; burstcnt |= temp << 8; if (burstcnt) return burstcnt; msleep(TPM_TIMEOUT); } while (time_before(jiffies, stop)); end: return -EBUSY; } /* get_burstcount() */ /* * wait_for_stat wait for a TPM_STS value * @param: chip, the tpm chip description * @param: mask, the value mask to wait * @param: timeout, the timeout * @param: queue, the wait queue. * @return: the tpm status, 0 if success, -ETIME if timeout is reached. */ static int wait_for_stat(struct tpm_chip *chip, u8 mask, unsigned long timeout, wait_queue_head_t *queue) { unsigned long stop; long rc; u8 status; if (chip->vendor.irq) { rc = wait_for_serirq_timeout(chip, ((tpm_stm_i2c_status (chip) & mask) == mask), timeout); if (rc > 0) return 0; } else { stop = jiffies + timeout; do { msleep(TPM_TIMEOUT); status = tpm_stm_i2c_status(chip); if ((status & mask) == mask) return 0; } while (time_before(jiffies, stop)); } return -ETIME; } /* wait_for_stat() */ /* * recv_data receive data * @param: chip, the tpm chip description * @param: buf, the buffer where the data are received * @param: count, the number of data to receive * @return: the number of bytes read from TPM FIFO. */ static int recv_data(struct tpm_chip *chip, u8 *buf, size_t count) { int size = 0, burstcnt, len, ret; struct i2c_client *client; client = (struct i2c_client *)TPM_VPRIV(chip); while (size < count && wait_for_stat(chip, TPM_STS_DATA_AVAIL | TPM_STS_VALID, chip->vendor.timeout_c, &chip->vendor.read_queue) == 0) { burstcnt = get_burstcount(chip); if (burstcnt < 0) return burstcnt; len = min_t(int, burstcnt, count - size); ret = I2C_READ_DATA(client, TPM_DATA_FIFO, buf + size, len); if (ret < 0) return ret; size += len; } return size; } /* * tpm_ioserirq_handler the serirq irq handler * @param: irq, the tpm chip description * @param: dev_id, the description of the chip * @return: the status of the handler. */ static irqreturn_t tpm_ioserirq_handler(int irq, void *dev_id) { struct tpm_chip *chip = dev_id; struct i2c_client *client; struct st33zp24_platform_data *pin_infos; disable_irq_nosync(irq); client = (struct i2c_client *)TPM_VPRIV(chip); pin_infos = client->dev.platform_data; complete(&pin_infos->irq_detection); return IRQ_HANDLED; } /* tpm_ioserirq_handler() */ /* * tpm_stm_i2c_send send TPM commands through the I2C bus. * * @param: chip, the tpm_chip description as specified in driver/char/tpm/tpm.h * @param: buf, the buffer to send. * @param: count, the number of bytes to send. * @return: In case of success the number of bytes sent. * In other case, a < 0 value describing the issue. */ static int tpm_stm_i2c_send(struct tpm_chip *chip, unsigned char *buf, size_t len) { u32 status, i, size; int burstcnt = 0; int ret; u8 data; struct i2c_client *client; if (chip == NULL) return -EBUSY; if (len < TPM_HEADER_SIZE) return -EBUSY; client = (struct i2c_client *)TPM_VPRIV(chip); client->flags = 0; ret = request_locality(chip); if (ret < 0) return ret; status = tpm_stm_i2c_status(chip); if ((status & TPM_STS_COMMAND_READY) == 0) { tpm_stm_i2c_cancel(chip); if (wait_for_stat (chip, TPM_STS_COMMAND_READY, chip->vendor.timeout_b, &chip->vendor.int_queue) < 0) { ret = -ETIME; goto out_err; } } for (i = 0; i < len - 1;) { burstcnt = get_burstcount(chip); if (burstcnt < 0) return burstcnt; size = min_t(int, len - i - 1, burstcnt); ret = I2C_WRITE_DATA(client, TPM_DATA_FIFO, buf + i, size); if (ret < 0) goto out_err; i += size; } status = tpm_stm_i2c_status(chip); if ((status & TPM_STS_DATA_EXPECT) == 0) { ret = -EIO; goto out_err; } ret = I2C_WRITE_DATA(client, TPM_DATA_FIFO, buf + len - 1, 1); if (ret < 0) goto out_err; status = tpm_stm_i2c_status(chip); if ((status & TPM_STS_DATA_EXPECT) != 0) { ret = -EIO; goto out_err; } data = TPM_STS_GO; I2C_WRITE_DATA(client, TPM_STS, &data, 1); return len; out_err: tpm_stm_i2c_cancel(chip); release_locality(chip); return ret; } /* * tpm_stm_i2c_recv received TPM response through the I2C bus. * @param: chip, the tpm_chip description as specified in driver/char/tpm/tpm.h. * @param: buf, the buffer to store datas. * @param: count, the number of bytes to send. * @return: In case of success the number of bytes received. * In other case, a < 0 value describing the issue. */ static int tpm_stm_i2c_recv(struct tpm_chip *chip, unsigned char *buf, size_t count) { int size = 0; int expected; if (chip == NULL) return -EBUSY; if (count < TPM_HEADER_SIZE) { size = -EIO; goto out; } size = recv_data(chip, buf, TPM_HEADER_SIZE); if (size < TPM_HEADER_SIZE) { dev_err(chip->dev, "Unable to read header\n"); goto out; } expected = be32_to_cpu(*(__be32 *)(buf + 2)); if (expected > count) { size = -EIO; goto out; } size += recv_data(chip, &buf[TPM_HEADER_SIZE], expected - TPM_HEADER_SIZE); if (size < expected) { dev_err(chip->dev, "Unable to read remainder of result\n"); size = -ETIME; goto out; } out: chip->ops->cancel(chip); release_locality(chip); return size; } static bool tpm_st33_i2c_req_canceled(struct tpm_chip *chip, u8 status) { return (status == TPM_STS_COMMAND_READY); } static const struct tpm_class_ops st_i2c_tpm = { .send = tpm_stm_i2c_send, .recv = tpm_stm_i2c_recv, .cancel = tpm_stm_i2c_cancel, .status = tpm_stm_i2c_status, .req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID, .req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID, .req_canceled = tpm_st33_i2c_req_canceled, }; static int interrupts; module_param(interrupts, int, 0444); MODULE_PARM_DESC(interrupts, "Enable interrupts"); static int power_mgt = 1; module_param(power_mgt, int, 0444); MODULE_PARM_DESC(power_mgt, "Power Management"); /* * tpm_st33_i2c_probe initialize the TPM device * @param: client, the i2c_client drescription (TPM I2C description). * @param: id, the i2c_device_id struct. * @return: 0 in case of success. * -1 in other case. */ static int tpm_st33_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { int err; u8 intmask; struct tpm_chip *chip; struct st33zp24_platform_data *platform_data; if (client == NULL) { pr_info("%s: i2c client is NULL. Device not accessible.\n", __func__); err = -ENODEV; goto end; } if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { dev_info(&client->dev, "client not i2c capable\n"); err = -ENODEV; goto end; } chip = tpm_register_hardware(&client->dev, &st_i2c_tpm); if (!chip) { dev_info(&client->dev, "fail chip\n"); err = -ENODEV; goto end; } platform_data = client->dev.platform_data; if (!platform_data) { dev_info(&client->dev, "chip not available\n"); err = -ENODEV; goto _tpm_clean_answer; } platform_data->tpm_i2c_buffer[0] = kmalloc(TPM_BUFSIZE * sizeof(u8), GFP_KERNEL); if (platform_data->tpm_i2c_buffer[0] == NULL) { err = -ENOMEM; goto _tpm_clean_answer; } platform_data->tpm_i2c_buffer[1] = kmalloc(TPM_BUFSIZE * sizeof(u8), GFP_KERNEL); if (platform_data->tpm_i2c_buffer[1] == NULL) { err = -ENOMEM; goto _tpm_clean_response1; } TPM_VPRIV(chip) = client; chip->vendor.timeout_a = msecs_to_jiffies(TIS_SHORT_TIMEOUT); chip->vendor.timeout_b = msecs_to_jiffies(TIS_LONG_TIMEOUT); chip->vendor.timeout_c = msecs_to_jiffies(TIS_SHORT_TIMEOUT); chip->vendor.timeout_d = msecs_to_jiffies(TIS_SHORT_TIMEOUT); chip->vendor.locality = LOCALITY0; if (power_mgt) { err = gpio_request(platform_data->io_lpcpd, "TPM IO_LPCPD"); if (err) goto _gpio_init1; gpio_set_value(platform_data->io_lpcpd, 1); } if (interrupts) { init_completion(&platform_data->irq_detection); if (request_locality(chip) != LOCALITY0) { err = -ENODEV; goto _tpm_clean_response2; } err = gpio_request(platform_data->io_serirq, "TPM IO_SERIRQ"); if (err) goto _gpio_init2; clear_interruption(client); err = request_irq(gpio_to_irq(platform_data->io_serirq), &tpm_ioserirq_handler, IRQF_TRIGGER_HIGH, "TPM SERIRQ management", chip); if (err < 0) { dev_err(chip->dev , "TPM SERIRQ signals %d not available\n", gpio_to_irq(platform_data->io_serirq)); goto _irq_set; } err = I2C_READ_DATA(client, TPM_INT_ENABLE, &intmask, 1); if (err < 0) goto _irq_set; intmask |= TPM_INTF_CMD_READY_INT | TPM_INTF_FIFO_AVALAIBLE_INT | TPM_INTF_WAKE_UP_READY_INT | TPM_INTF_LOCALITY_CHANGE_INT | TPM_INTF_STS_VALID_INT | TPM_INTF_DATA_AVAIL_INT; err = I2C_WRITE_DATA(client, TPM_INT_ENABLE, &intmask, 1); if (err < 0) goto _irq_set; intmask = TPM_GLOBAL_INT_ENABLE; err = I2C_WRITE_DATA(client, (TPM_INT_ENABLE + 3), &intmask, 1); if (err < 0) goto _irq_set; err = I2C_READ_DATA(client, TPM_INT_STATUS, &intmask, 1); if (err < 0) goto _irq_set; chip->vendor.irq = interrupts; tpm_gen_interrupt(chip); } tpm_get_timeouts(chip); tpm_do_selftest(chip); dev_info(chip->dev, "TPM I2C Initialized\n"); return 0; _irq_set: free_irq(gpio_to_irq(platform_data->io_serirq), (void *)chip); _gpio_init2: if (interrupts) gpio_free(platform_data->io_serirq); _gpio_init1: if (power_mgt) gpio_free(platform_data->io_lpcpd); _tpm_clean_response2: kzfree(platform_data->tpm_i2c_buffer[1]); platform_data->tpm_i2c_buffer[1] = NULL; _tpm_clean_response1: kzfree(platform_data->tpm_i2c_buffer[0]); platform_data->tpm_i2c_buffer[0] = NULL; _tpm_clean_answer: tpm_remove_hardware(chip->dev); end: pr_info("TPM I2C initialisation fail\n"); return err; } /* * tpm_st33_i2c_remove remove the TPM device * @param: client, the i2c_client drescription (TPM I2C description). clear_bit(0, &chip->is_open); * @return: 0 in case of success. */ static int tpm_st33_i2c_remove(struct i2c_client *client) { struct tpm_chip *chip = (struct tpm_chip *)i2c_get_clientdata(client); struct st33zp24_platform_data *pin_infos = ((struct i2c_client *)TPM_VPRIV(chip))->dev.platform_data; if (pin_infos != NULL) { free_irq(pin_infos->io_serirq, chip); gpio_free(pin_infos->io_serirq); gpio_free(pin_infos->io_lpcpd); tpm_remove_hardware(chip->dev); if (pin_infos->tpm_i2c_buffer[1] != NULL) { kzfree(pin_infos->tpm_i2c_buffer[1]); pin_infos->tpm_i2c_buffer[1] = NULL; } if (pin_infos->tpm_i2c_buffer[0] != NULL) { kzfree(pin_infos->tpm_i2c_buffer[0]); pin_infos->tpm_i2c_buffer[0] = NULL; } } return 0; } #ifdef CONFIG_PM_SLEEP /* * tpm_st33_i2c_pm_suspend suspend the TPM device * @param: client, the i2c_client drescription (TPM I2C description). * @param: mesg, the power management message. * @return: 0 in case of success. */ static int tpm_st33_i2c_pm_suspend(struct device *dev) { struct st33zp24_platform_data *pin_infos = dev->platform_data; int ret = 0; if (power_mgt) { gpio_set_value(pin_infos->io_lpcpd, 0); } else { ret = tpm_pm_suspend(dev); } return ret; } /* tpm_st33_i2c_suspend() */ /* * tpm_st33_i2c_pm_resume resume the TPM device * @param: client, the i2c_client drescription (TPM I2C description). * @return: 0 in case of success. */ static int tpm_st33_i2c_pm_resume(struct device *dev) { struct tpm_chip *chip = dev_get_drvdata(dev); struct st33zp24_platform_data *pin_infos = dev->platform_data; int ret = 0; if (power_mgt) { gpio_set_value(pin_infos->io_lpcpd, 1); ret = wait_for_serirq_timeout(chip, (chip->ops->status(chip) & TPM_STS_VALID) == TPM_STS_VALID, chip->vendor.timeout_b); } else { ret = tpm_pm_resume(dev); if (!ret) tpm_do_selftest(chip); } return ret; } /* tpm_st33_i2c_pm_resume() */ #endif static const struct i2c_device_id tpm_st33_i2c_id[] = { {TPM_ST33_I2C, 0}, {} }; MODULE_DEVICE_TABLE(i2c, tpm_st33_i2c_id); static SIMPLE_DEV_PM_OPS(tpm_st33_i2c_ops, tpm_st33_i2c_pm_suspend, tpm_st33_i2c_pm_resume); static struct i2c_driver tpm_st33_i2c_driver = { .driver = { .owner = THIS_MODULE, .name = TPM_ST33_I2C, .pm = &tpm_st33_i2c_ops, }, .probe = tpm_st33_i2c_probe, .remove = tpm_st33_i2c_remove, .id_table = tpm_st33_i2c_id }; module_i2c_driver(tpm_st33_i2c_driver); MODULE_AUTHOR("Christophe Ricard (tpmsupport@st.com)"); MODULE_DESCRIPTION("STM TPM I2C ST33 Driver"); MODULE_VERSION("1.2.0"); MODULE_LICENSE("GPL");