2019-06-01 10:08:55 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2016-05-19 00:35:53 +02:00
/*
* Copyright ( C ) 2015 Infineon Technologies AG
* Copyright ( C ) 2016 STMicroelectronics SAS
*
* Authors :
* Peter Huewe < peter . huewe @ infineon . com >
* Christophe Ricard < christophe - h . ricard @ st . com >
*
* Maintained by : < tpmdd - devel @ lists . sourceforge . net >
*
* Device driver for TCG / TCPA TPM ( trusted platform module ) .
* Specifications at www . trustedcomputinggroup . org
*
* This device driver implements the TPM interface as defined in
* the TCG TPM Interface Spec version 1.3 , revision 27 via _raw / native
* SPI access_ .
*
* It is based on the original tpm_tis device driver from Leendert van
* Dorn and Kyleen Hall and Jarko Sakkinnen .
*/
2019-09-20 11:32:39 -07:00
# include <linux/acpi.h>
2019-09-20 11:32:38 -07:00
# include <linux/completion.h>
2016-05-19 00:35:53 +02:00
# include <linux/init.h>
2019-09-20 11:32:39 -07:00
# include <linux/interrupt.h>
# include <linux/kernel.h>
2016-05-19 00:35:53 +02:00
# include <linux/module.h>
# include <linux/slab.h>
2023-07-28 07:48:02 -06:00
# include <linux/of.h>
2019-09-20 11:32:39 -07:00
# include <linux/spi/spi.h>
2016-05-19 00:35:53 +02:00
# include <linux/tpm.h>
2019-09-20 11:32:39 -07:00
2016-05-19 00:35:53 +02:00
# include "tpm.h"
# include "tpm_tis_core.h"
2019-09-20 11:32:38 -07:00
# include "tpm_tis_spi.h"
2016-05-19 00:35:53 +02:00
# define MAX_SPI_FRAMESIZE 64
2019-09-20 11:32:37 -07:00
/*
* TCG SPI flow control is documented in section 6.4 of the spec [ 1 ] . In short ,
* keep trying to read from the device until MISO goes high indicating the
* wait state has ended .
*
* [ 1 ] https : //trustedcomputinggroup.org/resource/pc-client-platform-tpm-profile-ptp-specification/
*/
static int tpm_tis_spi_flow_control ( struct tpm_tis_spi_phy * phy ,
struct spi_transfer * spi_xfer )
{
struct spi_message m ;
int ret , i ;
if ( ( phy - > iobuf [ 3 ] & 0x01 ) = = 0 ) {
// handle SPI wait states
for ( i = 0 ; i < TPM_RETRY ; i + + ) {
spi_xfer - > len = 1 ;
spi_message_init ( & m ) ;
spi_message_add_tail ( spi_xfer , & m ) ;
ret = spi_sync_locked ( phy - > spi_device , & m ) ;
if ( ret < 0 )
return ret ;
if ( phy - > iobuf [ 0 ] & 0x01 )
break ;
}
if ( i = = TPM_RETRY )
return - ETIMEDOUT ;
}
return 0 ;
}
2023-04-21 14:43:08 +05:30
/*
* Half duplex controller with support for TPM wait state detection like
* Tegra QSPI need CMD , ADDR & DATA sent in single message to manage HW flow
* control . Each phase sent in different transfer for controller to idenity
* phase .
*/
static int tpm_tis_spi_transfer_half ( struct tpm_tis_data * data , u32 addr ,
u16 len , u8 * in , const u8 * out )
{
struct tpm_tis_spi_phy * phy = to_tpm_tis_spi_phy ( data ) ;
struct spi_transfer spi_xfer [ 3 ] ;
struct spi_message m ;
u8 transfer_len ;
int ret ;
while ( len ) {
transfer_len = min_t ( u16 , len , MAX_SPI_FRAMESIZE ) ;
spi_message_init ( & m ) ;
phy - > iobuf [ 0 ] = ( in ? 0x80 : 0 ) | ( transfer_len - 1 ) ;
phy - > iobuf [ 1 ] = 0xd4 ;
phy - > iobuf [ 2 ] = addr > > 8 ;
phy - > iobuf [ 3 ] = addr ;
memset ( & spi_xfer , 0 , sizeof ( spi_xfer ) ) ;
spi_xfer [ 0 ] . tx_buf = phy - > iobuf ;
spi_xfer [ 0 ] . len = 1 ;
spi_message_add_tail ( & spi_xfer [ 0 ] , & m ) ;
spi_xfer [ 1 ] . tx_buf = phy - > iobuf + 1 ;
spi_xfer [ 1 ] . len = 3 ;
spi_message_add_tail ( & spi_xfer [ 1 ] , & m ) ;
if ( out ) {
spi_xfer [ 2 ] . tx_buf = & phy - > iobuf [ 4 ] ;
spi_xfer [ 2 ] . rx_buf = NULL ;
memcpy ( & phy - > iobuf [ 4 ] , out , transfer_len ) ;
out + = transfer_len ;
}
if ( in ) {
spi_xfer [ 2 ] . tx_buf = NULL ;
spi_xfer [ 2 ] . rx_buf = & phy - > iobuf [ 4 ] ;
}
spi_xfer [ 2 ] . len = transfer_len ;
spi_message_add_tail ( & spi_xfer [ 2 ] , & m ) ;
reinit_completion ( & phy - > ready ) ;
ret = spi_sync ( phy - > spi_device , & m ) ;
if ( ret < 0 )
return ret ;
if ( in ) {
memcpy ( in , & phy - > iobuf [ 4 ] , transfer_len ) ;
in + = transfer_len ;
}
len - = transfer_len ;
}
return ret ;
}
static int tpm_tis_spi_transfer_full ( struct tpm_tis_data * data , u32 addr ,
u16 len , u8 * in , const u8 * out )
2016-05-19 00:35:53 +02:00
{
struct tpm_tis_spi_phy * phy = to_tpm_tis_spi_phy ( data ) ;
2017-03-02 13:03:14 +00:00
int ret = 0 ;
2016-05-19 00:35:53 +02:00
struct spi_message m ;
2017-03-02 13:03:14 +00:00
struct spi_transfer spi_xfer ;
u8 transfer_len ;
2016-05-19 00:35:53 +02:00
2017-03-02 13:03:14 +00:00
spi_bus_lock ( phy - > spi_device - > master ) ;
2016-05-19 00:35:53 +02:00
2017-03-02 13:03:14 +00:00
while ( len ) {
transfer_len = min_t ( u16 , len , MAX_SPI_FRAMESIZE ) ;
2016-05-19 00:35:53 +02:00
2017-09-11 12:26:52 +02:00
phy - > iobuf [ 0 ] = ( in ? 0x80 : 0 ) | ( transfer_len - 1 ) ;
phy - > iobuf [ 1 ] = 0xd4 ;
phy - > iobuf [ 2 ] = addr > > 8 ;
phy - > iobuf [ 3 ] = addr ;
2016-05-19 00:35:53 +02:00
2017-03-02 13:03:14 +00:00
memset ( & spi_xfer , 0 , sizeof ( spi_xfer ) ) ;
2017-09-11 12:26:52 +02:00
spi_xfer . tx_buf = phy - > iobuf ;
spi_xfer . rx_buf = phy - > iobuf ;
2017-03-02 13:03:14 +00:00
spi_xfer . len = 4 ;
spi_xfer . cs_change = 1 ;
spi_message_init ( & m ) ;
spi_message_add_tail ( & spi_xfer , & m ) ;
ret = spi_sync_locked ( phy - > spi_device , & m ) ;
if ( ret < 0 )
goto exit ;
2020-05-28 15:19:30 -07:00
/* Flow control transfers are receive only */
spi_xfer . tx_buf = NULL ;
2019-09-20 11:32:37 -07:00
ret = phy - > flow_control ( phy , & spi_xfer ) ;
if ( ret < 0 )
goto exit ;
2017-03-02 13:03:13 +00:00
2017-03-02 13:03:14 +00:00
spi_xfer . cs_change = 0 ;
spi_xfer . len = transfer_len ;
2019-12-17 11:16:15 +02:00
spi_xfer . delay . value = 5 ;
spi_xfer . delay . unit = SPI_DELAY_UNIT_USECS ;
2017-09-11 12:26:52 +02:00
2020-05-28 15:19:30 -07:00
if ( out ) {
spi_xfer . tx_buf = phy - > iobuf ;
2017-09-11 12:26:52 +02:00
spi_xfer . rx_buf = NULL ;
memcpy ( phy - > iobuf , out , transfer_len ) ;
out + = transfer_len ;
}
2017-03-02 13:03:12 +00:00
2017-03-02 13:03:14 +00:00
spi_message_init ( & m ) ;
spi_message_add_tail ( & spi_xfer , & m ) ;
2019-09-20 11:32:38 -07:00
reinit_completion ( & phy - > ready ) ;
2017-03-02 13:03:14 +00:00
ret = spi_sync_locked ( phy - > spi_device , & m ) ;
if ( ret < 0 )
goto exit ;
2017-03-02 13:03:11 +00:00
2017-09-11 12:26:52 +02:00
if ( in ) {
memcpy ( in , phy - > iobuf , transfer_len ) ;
2017-09-07 15:30:45 +02:00
in + = transfer_len ;
2017-09-11 12:26:52 +02:00
}
len - = transfer_len ;
2017-03-02 13:03:11 +00:00
}
2016-05-19 00:35:53 +02:00
exit :
2023-05-23 10:45:36 +08:00
if ( ret < 0 ) {
/* Deactivate chip select */
memset ( & spi_xfer , 0 , sizeof ( spi_xfer ) ) ;
spi_message_init ( & m ) ;
spi_message_add_tail ( & spi_xfer , & m ) ;
spi_sync_locked ( phy - > spi_device , & m ) ;
}
2016-05-19 00:35:53 +02:00
spi_bus_unlock ( phy - > spi_device - > master ) ;
return ret ;
}
2023-04-21 14:43:08 +05:30
int tpm_tis_spi_transfer ( struct tpm_tis_data * data , u32 addr , u16 len ,
u8 * in , const u8 * out )
{
struct tpm_tis_spi_phy * phy = to_tpm_tis_spi_phy ( data ) ;
struct spi_controller * ctlr = phy - > spi_device - > controller ;
/*
* TPM flow control over SPI requires full duplex support .
* Send entire message to a half duplex controller to handle
* wait polling in controller .
* Set TPM HW flow control flag . .
*/
if ( ctlr - > flags & SPI_CONTROLLER_HALF_DUPLEX )
return tpm_tis_spi_transfer_half ( data , addr , len , in , out ) ;
else
return tpm_tis_spi_transfer_full ( data , addr , len , in , out ) ;
}
2017-03-02 13:03:11 +00:00
static int tpm_tis_spi_read_bytes ( struct tpm_tis_data * data , u32 addr ,
2022-03-21 10:09:24 +01:00
u16 len , u8 * result , enum tpm_tis_io_mode io_mode )
2017-03-02 13:03:11 +00:00
{
2017-09-07 15:30:45 +02:00
return tpm_tis_spi_transfer ( data , addr , len , result , NULL ) ;
2017-03-02 13:03:11 +00:00
}
2016-05-19 00:35:53 +02:00
static int tpm_tis_spi_write_bytes ( struct tpm_tis_data * data , u32 addr ,
2022-03-21 10:09:24 +01:00
u16 len , const u8 * value , enum tpm_tis_io_mode io_mode )
2016-05-19 00:35:53 +02:00
{
2017-09-07 15:30:45 +02:00
return tpm_tis_spi_transfer ( data , addr , len , NULL , value ) ;
2016-05-19 00:35:53 +02:00
}
2019-09-20 11:32:38 -07:00
int tpm_tis_spi_init ( struct spi_device * spi , struct tpm_tis_spi_phy * phy ,
int irq , const struct tpm_tis_phy_ops * phy_ops )
{
phy - > iobuf = devm_kmalloc ( & spi - > dev , MAX_SPI_FRAMESIZE , GFP_KERNEL ) ;
if ( ! phy - > iobuf )
return - ENOMEM ;
phy - > spi_device = spi ;
return tpm_tis_core_init ( & spi - > dev , & phy - > priv , irq , phy_ops , NULL ) ;
}
2016-05-19 00:35:53 +02:00
static const struct tpm_tis_phy_ops tpm_spi_phy_ops = {
. read_bytes = tpm_tis_spi_read_bytes ,
. write_bytes = tpm_tis_spi_write_bytes ,
} ;
static int tpm_tis_spi_probe ( struct spi_device * dev )
{
struct tpm_tis_spi_phy * phy ;
2018-06-08 09:09:07 +02:00
int irq ;
2016-05-19 00:35:53 +02:00
phy = devm_kzalloc ( & dev - > dev , sizeof ( struct tpm_tis_spi_phy ) ,
GFP_KERNEL ) ;
if ( ! phy )
return - ENOMEM ;
2019-09-20 11:32:37 -07:00
phy - > flow_control = tpm_tis_spi_flow_control ;
2017-09-11 12:26:52 +02:00
2023-04-21 14:43:08 +05:30
if ( dev - > controller - > flags & SPI_CONTROLLER_HALF_DUPLEX )
dev - > mode | = SPI_TPM_HW_FLOW ;
2018-06-08 09:09:07 +02:00
/* If the SPI device has an IRQ then use that */
if ( dev - > irq > 0 )
irq = dev - > irq ;
else
irq = - 1 ;
2019-09-20 11:32:38 -07:00
init_completion ( & phy - > ready ) ;
return tpm_tis_spi_init ( dev , phy , irq , & tpm_spi_phy_ops ) ;
}
typedef int ( * tpm_tis_spi_probe_func ) ( struct spi_device * ) ;
static int tpm_tis_spi_driver_probe ( struct spi_device * spi )
{
const struct spi_device_id * spi_dev_id = spi_get_device_id ( spi ) ;
tpm_tis_spi_probe_func probe_func ;
probe_func = of_device_get_match_data ( & spi - > dev ) ;
2021-05-07 22:52:55 +08:00
if ( ! probe_func ) {
if ( spi_dev_id ) {
probe_func = ( tpm_tis_spi_probe_func ) spi_dev_id - > driver_data ;
if ( ! probe_func )
return - ENODEV ;
} else
probe_func = tpm_tis_spi_probe ;
}
2019-09-20 11:32:38 -07:00
return probe_func ( spi ) ;
2016-05-19 00:35:53 +02:00
}
2019-09-20 11:32:38 -07:00
static SIMPLE_DEV_PM_OPS ( tpm_tis_pm , tpm_pm_suspend , tpm_tis_spi_resume ) ;
2016-05-19 00:35:53 +02:00
2022-01-23 18:52:01 +01:00
static void tpm_tis_spi_remove ( struct spi_device * dev )
2016-05-19 00:35:53 +02:00
{
struct tpm_chip * chip = spi_get_drvdata ( dev ) ;
tpm_chip_unregister ( chip ) ;
tpm_tis_remove ( chip ) ;
}
static const struct spi_device_id tpm_tis_spi_id [ ] = {
tpm_tis_spi: add missing SPI device ID entries
The SPI core always reports a "MODALIAS=spi:<foo>", even if the device was
registered via OF. This means that this module won't auto-load if a DT has
for example has a node with a compatible "infineon,slb9670" string.
In that case kmod will expect a "MODALIAS=of:N*T*Cinfineon,slb9670" uevent
but instead will get a "MODALIAS=spi:slb9670", which is not present in the
kernel module aliases:
$ modinfo drivers/char/tpm/tpm_tis_spi.ko | grep alias
alias: of:N*T*Cgoogle,cr50C*
alias: of:N*T*Cgoogle,cr50
alias: of:N*T*Ctcg,tpm_tis-spiC*
alias: of:N*T*Ctcg,tpm_tis-spi
alias: of:N*T*Cinfineon,slb9670C*
alias: of:N*T*Cinfineon,slb9670
alias: of:N*T*Cst,st33htpm-spiC*
alias: of:N*T*Cst,st33htpm-spi
alias: spi:cr50
alias: spi:tpm_tis_spi
alias: acpi*:SMO0768:*
To workaround this issue, add in the SPI device ID table all the entries
that are present in the OF device ID table.
Reported-by: Alexander Wellbrock <a.wellbrock@mailbox.org>
Signed-off-by: Javier Martinez Canillas <javierm@redhat.com>
Tested-by: Peter Robinson <pbrobinson@gmail.com>
Reviewed-by: Jarkko Sakkinen <jarkko@kernel.org>
Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
2021-05-27 17:23:52 +02:00
{ " st33htpm-spi " , ( unsigned long ) tpm_tis_spi_probe } ,
{ " slb9670 " , ( unsigned long ) tpm_tis_spi_probe } ,
2019-09-20 11:32:38 -07:00
{ " tpm_tis_spi " , ( unsigned long ) tpm_tis_spi_probe } ,
2021-09-24 15:41:11 +01:00
{ " tpm_tis-spi " , ( unsigned long ) tpm_tis_spi_probe } ,
2019-09-20 11:32:38 -07:00
{ " cr50 " , ( unsigned long ) cr50_spi_probe } ,
2016-05-19 00:35:53 +02:00
{ }
} ;
MODULE_DEVICE_TABLE ( spi , tpm_tis_spi_id ) ;
2023-03-11 18:35:41 +01:00
static const struct of_device_id of_tis_spi_match [ ] __maybe_unused = {
2019-09-20 11:32:38 -07:00
{ . compatible = " st,st33htpm-spi " , . data = tpm_tis_spi_probe } ,
{ . compatible = " infineon,slb9670 " , . data = tpm_tis_spi_probe } ,
{ . compatible = " tcg,tpm_tis-spi " , . data = tpm_tis_spi_probe } ,
{ . compatible = " google,cr50 " , . data = cr50_spi_probe } ,
2016-05-19 00:35:53 +02:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , of_tis_spi_match ) ;
2023-03-11 18:35:41 +01:00
static const struct acpi_device_id acpi_tis_spi_match [ ] __maybe_unused = {
2016-05-19 00:35:53 +02:00
{ " SMO0768 " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( acpi , acpi_tis_spi_match ) ;
static struct spi_driver tpm_tis_spi_driver = {
. driver = {
. name = " tpm_tis_spi " ,
. pm = & tpm_tis_pm ,
. of_match_table = of_match_ptr ( of_tis_spi_match ) ,
. acpi_match_table = ACPI_PTR ( acpi_tis_spi_match ) ,
2020-06-19 14:20:01 -07:00
. probe_type = PROBE_PREFER_ASYNCHRONOUS ,
2016-05-19 00:35:53 +02:00
} ,
2019-09-20 11:32:38 -07:00
. probe = tpm_tis_spi_driver_probe ,
2016-05-19 00:35:53 +02:00
. remove = tpm_tis_spi_remove ,
. id_table = tpm_tis_spi_id ,
} ;
module_spi_driver ( tpm_tis_spi_driver ) ;
MODULE_DESCRIPTION ( " TPM Driver for native SPI access " ) ;
MODULE_LICENSE ( " GPL " ) ;