| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2004 IBM Corporation |
| * |
| * Authors: |
| * Leendert van Doorn <leendert@watson.ibm.com> |
| * Dave Safford <safford@watson.ibm.com> |
| * Reiner Sailer <sailer@watson.ibm.com> |
| * Kylene Hall <kjhall@us.ibm.com> |
| * |
| * Maintained by: <tpmdd-devel@lists.sourceforge.net> |
| * |
| * Device driver for TCG/TCPA TPM (trusted platform module). |
| * Specifications at www.trustedcomputinggroup.org |
| */ |
| |
| #include "tpm.h" |
| #include "tpm_atmel.h" |
| |
| /* write status bits */ |
| enum tpm_atmel_write_status { |
| ATML_STATUS_ABORT = 0x01, |
| ATML_STATUS_LASTBYTE = 0x04 |
| }; |
| /* read status bits */ |
| enum tpm_atmel_read_status { |
| ATML_STATUS_BUSY = 0x01, |
| ATML_STATUS_DATA_AVAIL = 0x02, |
| ATML_STATUS_REWRITE = 0x04, |
| ATML_STATUS_READY = 0x08 |
| }; |
| |
| static int tpm_atml_recv(struct tpm_chip *chip, u8 *buf, size_t count) |
| { |
| struct tpm_atmel_priv *priv = dev_get_drvdata(&chip->dev); |
| u8 status, *hdr = buf; |
| u32 size; |
| int i; |
| __be32 *native_size; |
| |
| /* start reading header */ |
| if (count < 6) |
| return -EIO; |
| |
| for (i = 0; i < 6; i++) { |
| status = ioread8(priv->iobase + 1); |
| if ((status & ATML_STATUS_DATA_AVAIL) == 0) { |
| dev_err(&chip->dev, "error reading header\n"); |
| return -EIO; |
| } |
| *buf++ = ioread8(priv->iobase); |
| } |
| |
| /* size of the data received */ |
| native_size = (__force __be32 *) (hdr + 2); |
| size = be32_to_cpu(*native_size); |
| |
| if (count < size) { |
| dev_err(&chip->dev, |
| "Recv size(%d) less than available space\n", size); |
| for (; i < size; i++) { /* clear the waiting data anyway */ |
| status = ioread8(priv->iobase + 1); |
| if ((status & ATML_STATUS_DATA_AVAIL) == 0) { |
| dev_err(&chip->dev, "error reading data\n"); |
| return -EIO; |
| } |
| } |
| return -EIO; |
| } |
| |
| /* read all the data available */ |
| for (; i < size; i++) { |
| status = ioread8(priv->iobase + 1); |
| if ((status & ATML_STATUS_DATA_AVAIL) == 0) { |
| dev_err(&chip->dev, "error reading data\n"); |
| return -EIO; |
| } |
| *buf++ = ioread8(priv->iobase); |
| } |
| |
| /* make sure data available is gone */ |
| status = ioread8(priv->iobase + 1); |
| |
| if (status & ATML_STATUS_DATA_AVAIL) { |
| dev_err(&chip->dev, "data available is stuck\n"); |
| return -EIO; |
| } |
| |
| return size; |
| } |
| |
| static int tpm_atml_send(struct tpm_chip *chip, u8 *buf, size_t count) |
| { |
| struct tpm_atmel_priv *priv = dev_get_drvdata(&chip->dev); |
| int i; |
| |
| dev_dbg(&chip->dev, "tpm_atml_send:\n"); |
| for (i = 0; i < count; i++) { |
| dev_dbg(&chip->dev, "%d 0x%x(%d)\n", i, buf[i], buf[i]); |
| iowrite8(buf[i], priv->iobase); |
| } |
| |
| return 0; |
| } |
| |
| static void tpm_atml_cancel(struct tpm_chip *chip) |
| { |
| struct tpm_atmel_priv *priv = dev_get_drvdata(&chip->dev); |
| |
| iowrite8(ATML_STATUS_ABORT, priv->iobase + 1); |
| } |
| |
| static u8 tpm_atml_status(struct tpm_chip *chip) |
| { |
| struct tpm_atmel_priv *priv = dev_get_drvdata(&chip->dev); |
| |
| return ioread8(priv->iobase + 1); |
| } |
| |
| static bool tpm_atml_req_canceled(struct tpm_chip *chip, u8 status) |
| { |
| return (status == ATML_STATUS_READY); |
| } |
| |
| static const struct tpm_class_ops tpm_atmel = { |
| .recv = tpm_atml_recv, |
| .send = tpm_atml_send, |
| .cancel = tpm_atml_cancel, |
| .status = tpm_atml_status, |
| .req_complete_mask = ATML_STATUS_BUSY | ATML_STATUS_DATA_AVAIL, |
| .req_complete_val = ATML_STATUS_DATA_AVAIL, |
| .req_canceled = tpm_atml_req_canceled, |
| }; |
| |
| static struct platform_device *pdev; |
| |
| static void atml_plat_remove(void) |
| { |
| struct tpm_chip *chip = dev_get_drvdata(&pdev->dev); |
| struct tpm_atmel_priv *priv = dev_get_drvdata(&chip->dev); |
| |
| tpm_chip_unregister(chip); |
| if (priv->have_region) |
| atmel_release_region(priv->base, priv->region_size); |
| atmel_put_base_addr(priv->iobase); |
| platform_device_unregister(pdev); |
| } |
| |
| static SIMPLE_DEV_PM_OPS(tpm_atml_pm, tpm_pm_suspend, tpm_pm_resume); |
| |
| static struct platform_driver atml_drv = { |
| .driver = { |
| .name = "tpm_atmel", |
| .pm = &tpm_atml_pm, |
| }, |
| }; |
| |
| static int __init init_atmel(void) |
| { |
| int rc = 0; |
| void __iomem *iobase = NULL; |
| int have_region, region_size; |
| unsigned long base; |
| struct tpm_chip *chip; |
| struct tpm_atmel_priv *priv; |
| |
| rc = platform_driver_register(&atml_drv); |
| if (rc) |
| return rc; |
| |
| if ((iobase = atmel_get_base_addr(&base, ®ion_size)) == NULL) { |
| rc = -ENODEV; |
| goto err_unreg_drv; |
| } |
| |
| have_region = |
| (atmel_request_region |
| (base, region_size, "tpm_atmel0") == NULL) ? 0 : 1; |
| |
| pdev = platform_device_register_simple("tpm_atmel", -1, NULL, 0); |
| if (IS_ERR(pdev)) { |
| rc = PTR_ERR(pdev); |
| goto err_rel_reg; |
| } |
| |
| priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); |
| if (!priv) { |
| rc = -ENOMEM; |
| goto err_unreg_dev; |
| } |
| |
| priv->iobase = iobase; |
| priv->base = base; |
| priv->have_region = have_region; |
| priv->region_size = region_size; |
| |
| chip = tpmm_chip_alloc(&pdev->dev, &tpm_atmel); |
| if (IS_ERR(chip)) { |
| rc = PTR_ERR(chip); |
| goto err_unreg_dev; |
| } |
| |
| dev_set_drvdata(&chip->dev, priv); |
| |
| rc = tpm_chip_register(chip); |
| if (rc) |
| goto err_unreg_dev; |
| |
| return 0; |
| |
| err_unreg_dev: |
| platform_device_unregister(pdev); |
| err_rel_reg: |
| atmel_put_base_addr(iobase); |
| if (have_region) |
| atmel_release_region(base, |
| region_size); |
| err_unreg_drv: |
| platform_driver_unregister(&atml_drv); |
| return rc; |
| } |
| |
| static void __exit cleanup_atmel(void) |
| { |
| platform_driver_unregister(&atml_drv); |
| atml_plat_remove(); |
| } |
| |
| module_init(init_atmel); |
| module_exit(cleanup_atmel); |
| |
| MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)"); |
| MODULE_DESCRIPTION("TPM Driver"); |
| MODULE_VERSION("2.0"); |
| MODULE_LICENSE("GPL"); |