static int tpm_inf_pnp_probe()

in tpm/tpm_infineon.c [388:572]


static int tpm_inf_pnp_probe(struct pnp_dev *dev,
				       const struct pnp_device_id *dev_id)
{
	int rc = 0;
	u8 iol, ioh;
	int vendorid[2];
	int version[2];
	int productid[2];
	const char *chipname;
	struct tpm_chip *chip;

	/* read IO-ports through PnP */
	if (pnp_port_valid(dev, 0) && pnp_port_valid(dev, 1) &&
	    !(pnp_port_flags(dev, 0) & IORESOURCE_DISABLED)) {

		tpm_dev.iotype = TPM_INF_IO_PORT;

		tpm_dev.config_port = pnp_port_start(dev, 0);
		tpm_dev.config_size = pnp_port_len(dev, 0);
		tpm_dev.data_regs = pnp_port_start(dev, 1);
		tpm_dev.data_size = pnp_port_len(dev, 1);
		if ((tpm_dev.data_size < 4) || (tpm_dev.config_size < 2)) {
			rc = -EINVAL;
			goto err_last;
		}
		dev_info(&dev->dev, "Found %s with ID %s\n",
			 dev->name, dev_id->id);
		if (!((tpm_dev.data_regs >> 8) & 0xff)) {
			rc = -EINVAL;
			goto err_last;
		}
		/* publish my base address and request region */
		if (request_region(tpm_dev.data_regs, tpm_dev.data_size,
				   "tpm_infineon0") == NULL) {
			rc = -EINVAL;
			goto err_last;
		}
		if (request_region(tpm_dev.config_port, tpm_dev.config_size,
				   "tpm_infineon0") == NULL) {
			release_region(tpm_dev.data_regs, tpm_dev.data_size);
			rc = -EINVAL;
			goto err_last;
		}
	} else if (pnp_mem_valid(dev, 0) &&
		   !(pnp_mem_flags(dev, 0) & IORESOURCE_DISABLED)) {

		tpm_dev.iotype = TPM_INF_IO_MEM;

		tpm_dev.map_base = pnp_mem_start(dev, 0);
		tpm_dev.map_size = pnp_mem_len(dev, 0);

		dev_info(&dev->dev, "Found %s with ID %s\n",
			 dev->name, dev_id->id);

		/* publish my base address and request region */
		if (request_mem_region(tpm_dev.map_base, tpm_dev.map_size,
				       "tpm_infineon0") == NULL) {
			rc = -EINVAL;
			goto err_last;
		}

		tpm_dev.mem_base = ioremap(tpm_dev.map_base, tpm_dev.map_size);
		if (tpm_dev.mem_base == NULL) {
			release_mem_region(tpm_dev.map_base, tpm_dev.map_size);
			rc = -EINVAL;
			goto err_last;
		}

		/*
		 * The only known MMIO based Infineon TPM system provides
		 * a single large mem region with the device config
		 * registers at the default TPM_ADDR.  The data registers
		 * seem like they could be placed anywhere within the MMIO
		 * region, but lets just put them at zero offset.
		 */
		tpm_dev.index_off = TPM_ADDR;
		tpm_dev.data_regs = 0x0;
	} else {
		rc = -EINVAL;
		goto err_last;
	}

	/* query chip for its vendor, its version number a.s.o. */
	tpm_config_out(ENABLE_REGISTER_PAIR, TPM_INF_ADDR);
	tpm_config_out(IDVENL, TPM_INF_ADDR);
	vendorid[1] = tpm_config_in(TPM_INF_DATA);
	tpm_config_out(IDVENH, TPM_INF_ADDR);
	vendorid[0] = tpm_config_in(TPM_INF_DATA);
	tpm_config_out(IDPDL, TPM_INF_ADDR);
	productid[1] = tpm_config_in(TPM_INF_DATA);
	tpm_config_out(IDPDH, TPM_INF_ADDR);
	productid[0] = tpm_config_in(TPM_INF_DATA);
	tpm_config_out(CHIP_ID1, TPM_INF_ADDR);
	version[1] = tpm_config_in(TPM_INF_DATA);
	tpm_config_out(CHIP_ID2, TPM_INF_ADDR);
	version[0] = tpm_config_in(TPM_INF_DATA);

	switch ((productid[0] << 8) | productid[1]) {
	case 6:
		chipname = " (SLD 9630 TT 1.1)";
		break;
	case 11:
		chipname = " (SLB 9635 TT 1.2)";
		break;
	default:
		chipname = " (unknown chip)";
		break;
	}

	if ((vendorid[0] << 8 | vendorid[1]) == (TPM_INFINEON_DEV_VEN_VALUE)) {

		/* configure TPM with IO-ports */
		tpm_config_out(IOLIMH, TPM_INF_ADDR);
		tpm_config_out((tpm_dev.data_regs >> 8) & 0xff, TPM_INF_DATA);
		tpm_config_out(IOLIML, TPM_INF_ADDR);
		tpm_config_out((tpm_dev.data_regs & 0xff), TPM_INF_DATA);

		/* control if IO-ports are set correctly */
		tpm_config_out(IOLIMH, TPM_INF_ADDR);
		ioh = tpm_config_in(TPM_INF_DATA);
		tpm_config_out(IOLIML, TPM_INF_ADDR);
		iol = tpm_config_in(TPM_INF_DATA);

		if ((ioh << 8 | iol) != tpm_dev.data_regs) {
			dev_err(&dev->dev,
				"Could not set IO-data registers to 0x%x\n",
				tpm_dev.data_regs);
			rc = -EIO;
			goto err_release_region;
		}

		/* activate register */
		tpm_config_out(TPM_DAR, TPM_INF_ADDR);
		tpm_config_out(0x01, TPM_INF_DATA);
		tpm_config_out(DISABLE_REGISTER_PAIR, TPM_INF_ADDR);

		/* disable RESET, LP and IRQC */
		tpm_data_out(RESET_LP_IRQC_DISABLE, CMD);

		/* Finally, we're done, print some infos */
		dev_info(&dev->dev, "TPM found: "
			 "config base 0x%lx, "
			 "data base 0x%lx, "
			 "chip version 0x%02x%02x, "
			 "vendor id 0x%x%x (Infineon), "
			 "product id 0x%02x%02x"
			 "%s\n",
			 tpm_dev.iotype == TPM_INF_IO_PORT ?
			 tpm_dev.config_port :
			 tpm_dev.map_base + tpm_dev.index_off,
			 tpm_dev.iotype == TPM_INF_IO_PORT ?
			 tpm_dev.data_regs :
			 tpm_dev.map_base + tpm_dev.data_regs,
			 version[0], version[1],
			 vendorid[0], vendorid[1],
			 productid[0], productid[1], chipname);

		chip = tpmm_chip_alloc(&dev->dev, &tpm_inf);
		if (IS_ERR(chip)) {
			rc = PTR_ERR(chip);
			goto err_release_region;
		}

		rc = tpm_chip_register(chip);
		if (rc)
			goto err_release_region;

		return 0;
	} else {
		rc = -ENODEV;
		goto err_release_region;
	}

err_release_region:
	if (tpm_dev.iotype == TPM_INF_IO_PORT) {
		release_region(tpm_dev.data_regs, tpm_dev.data_size);
		release_region(tpm_dev.config_port, tpm_dev.config_size);
	} else {
		iounmap(tpm_dev.mem_base);
		release_mem_region(tpm_dev.map_base, tpm_dev.map_size);
	}

err_last:
	return rc;
}