162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  lpc_sch.c - LPC interface for Intel Poulsbo SCH
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  LPC bridge function of the Intel SCH contains many other
662306a36Sopenharmony_ci *  functional units, such as Interrupt controllers, Timers,
762306a36Sopenharmony_ci *  Power Management, System Management, GPIO, RTC, and LPC
862306a36Sopenharmony_ci *  Configuration Registers.
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci *  Copyright (c) 2010 CompuLab Ltd
1162306a36Sopenharmony_ci *  Copyright (c) 2014 Intel Corp.
1262306a36Sopenharmony_ci *  Author: Denis Turischev <denis@compulab.co.il>
1362306a36Sopenharmony_ci */
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <linux/kernel.h>
1662306a36Sopenharmony_ci#include <linux/module.h>
1762306a36Sopenharmony_ci#include <linux/errno.h>
1862306a36Sopenharmony_ci#include <linux/acpi.h>
1962306a36Sopenharmony_ci#include <linux/pci.h>
2062306a36Sopenharmony_ci#include <linux/mfd/core.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#define SMBASE		0x40
2362306a36Sopenharmony_ci#define SMBUS_IO_SIZE	64
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define GPIO_BASE	0x44
2662306a36Sopenharmony_ci#define GPIO_IO_SIZE	64
2762306a36Sopenharmony_ci#define GPIO_IO_SIZE_CENTERTON	128
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#define WDTBASE		0x84
3062306a36Sopenharmony_ci#define WDT_IO_SIZE	64
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cienum sch_chipsets {
3362306a36Sopenharmony_ci	LPC_SCH = 0,		/* Intel Poulsbo SCH */
3462306a36Sopenharmony_ci	LPC_ITC,		/* Intel Tunnel Creek */
3562306a36Sopenharmony_ci	LPC_CENTERTON,		/* Intel Centerton */
3662306a36Sopenharmony_ci	LPC_QUARK_X1000,	/* Intel Quark X1000 */
3762306a36Sopenharmony_ci};
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistruct lpc_sch_info {
4062306a36Sopenharmony_ci	unsigned int io_size_smbus;
4162306a36Sopenharmony_ci	unsigned int io_size_gpio;
4262306a36Sopenharmony_ci	unsigned int io_size_wdt;
4362306a36Sopenharmony_ci};
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_cistatic struct lpc_sch_info sch_chipset_info[] = {
4662306a36Sopenharmony_ci	[LPC_SCH] = {
4762306a36Sopenharmony_ci		.io_size_smbus = SMBUS_IO_SIZE,
4862306a36Sopenharmony_ci		.io_size_gpio = GPIO_IO_SIZE,
4962306a36Sopenharmony_ci	},
5062306a36Sopenharmony_ci	[LPC_ITC] = {
5162306a36Sopenharmony_ci		.io_size_smbus = SMBUS_IO_SIZE,
5262306a36Sopenharmony_ci		.io_size_gpio = GPIO_IO_SIZE,
5362306a36Sopenharmony_ci		.io_size_wdt = WDT_IO_SIZE,
5462306a36Sopenharmony_ci	},
5562306a36Sopenharmony_ci	[LPC_CENTERTON] = {
5662306a36Sopenharmony_ci		.io_size_smbus = SMBUS_IO_SIZE,
5762306a36Sopenharmony_ci		.io_size_gpio = GPIO_IO_SIZE_CENTERTON,
5862306a36Sopenharmony_ci		.io_size_wdt = WDT_IO_SIZE,
5962306a36Sopenharmony_ci	},
6062306a36Sopenharmony_ci	[LPC_QUARK_X1000] = {
6162306a36Sopenharmony_ci		.io_size_gpio = GPIO_IO_SIZE,
6262306a36Sopenharmony_ci		.io_size_wdt = WDT_IO_SIZE,
6362306a36Sopenharmony_ci	},
6462306a36Sopenharmony_ci};
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistatic const struct pci_device_id lpc_sch_ids[] = {
6762306a36Sopenharmony_ci	{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_SCH_LPC), LPC_SCH },
6862306a36Sopenharmony_ci	{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ITC_LPC), LPC_ITC },
6962306a36Sopenharmony_ci	{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CENTERTON_ILB), LPC_CENTERTON },
7062306a36Sopenharmony_ci	{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_QUARK_X1000_ILB), LPC_QUARK_X1000 },
7162306a36Sopenharmony_ci	{ 0, }
7262306a36Sopenharmony_ci};
7362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, lpc_sch_ids);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci#define LPC_NO_RESOURCE		1
7662306a36Sopenharmony_ci#define LPC_SKIP_RESOURCE	2
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistatic int lpc_sch_get_io(struct pci_dev *pdev, int where, const char *name,
7962306a36Sopenharmony_ci			  struct resource *res, int size)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	unsigned int base_addr_cfg;
8262306a36Sopenharmony_ci	unsigned short base_addr;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	if (size == 0)
8562306a36Sopenharmony_ci		return LPC_NO_RESOURCE;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	pci_read_config_dword(pdev, where, &base_addr_cfg);
8862306a36Sopenharmony_ci	base_addr = 0;
8962306a36Sopenharmony_ci	if (!(base_addr_cfg & (1 << 31)))
9062306a36Sopenharmony_ci		dev_warn(&pdev->dev, "Decode of the %s I/O range disabled\n",
9162306a36Sopenharmony_ci			 name);
9262306a36Sopenharmony_ci	else
9362306a36Sopenharmony_ci		base_addr = (unsigned short)base_addr_cfg;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	if (base_addr == 0) {
9662306a36Sopenharmony_ci		dev_warn(&pdev->dev, "I/O space for %s uninitialized\n", name);
9762306a36Sopenharmony_ci		return LPC_SKIP_RESOURCE;
9862306a36Sopenharmony_ci	}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	res->start = base_addr;
10162306a36Sopenharmony_ci	res->end = base_addr + size - 1;
10262306a36Sopenharmony_ci	res->flags = IORESOURCE_IO;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	return 0;
10562306a36Sopenharmony_ci}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cistatic int lpc_sch_populate_cell(struct pci_dev *pdev, int where,
10862306a36Sopenharmony_ci				 const char *name, int size, int id,
10962306a36Sopenharmony_ci				 struct mfd_cell *cell)
11062306a36Sopenharmony_ci{
11162306a36Sopenharmony_ci	struct resource *res;
11262306a36Sopenharmony_ci	int ret;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	res = devm_kzalloc(&pdev->dev, sizeof(*res), GFP_KERNEL);
11562306a36Sopenharmony_ci	if (!res)
11662306a36Sopenharmony_ci		return -ENOMEM;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	ret = lpc_sch_get_io(pdev, where, name, res, size);
11962306a36Sopenharmony_ci	if (ret)
12062306a36Sopenharmony_ci		return ret;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	memset(cell, 0, sizeof(*cell));
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	cell->name = name;
12562306a36Sopenharmony_ci	cell->resources = res;
12662306a36Sopenharmony_ci	cell->num_resources = 1;
12762306a36Sopenharmony_ci	cell->ignore_resource_conflicts = true;
12862306a36Sopenharmony_ci	cell->id = id;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	return 0;
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_cistatic int lpc_sch_probe(struct pci_dev *dev, const struct pci_device_id *id)
13462306a36Sopenharmony_ci{
13562306a36Sopenharmony_ci	struct mfd_cell lpc_sch_cells[3];
13662306a36Sopenharmony_ci	struct lpc_sch_info *info = &sch_chipset_info[id->driver_data];
13762306a36Sopenharmony_ci	unsigned int cells = 0;
13862306a36Sopenharmony_ci	int ret;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	ret = lpc_sch_populate_cell(dev, SMBASE, "isch_smbus",
14162306a36Sopenharmony_ci				    info->io_size_smbus,
14262306a36Sopenharmony_ci				    id->device, &lpc_sch_cells[cells]);
14362306a36Sopenharmony_ci	if (ret < 0)
14462306a36Sopenharmony_ci		return ret;
14562306a36Sopenharmony_ci	if (ret == 0)
14662306a36Sopenharmony_ci		cells++;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	ret = lpc_sch_populate_cell(dev, GPIO_BASE, "sch_gpio",
14962306a36Sopenharmony_ci				    info->io_size_gpio,
15062306a36Sopenharmony_ci				    id->device, &lpc_sch_cells[cells]);
15162306a36Sopenharmony_ci	if (ret < 0)
15262306a36Sopenharmony_ci		return ret;
15362306a36Sopenharmony_ci	if (ret == 0)
15462306a36Sopenharmony_ci		cells++;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	ret = lpc_sch_populate_cell(dev, WDTBASE, "ie6xx_wdt",
15762306a36Sopenharmony_ci				    info->io_size_wdt,
15862306a36Sopenharmony_ci				    id->device, &lpc_sch_cells[cells]);
15962306a36Sopenharmony_ci	if (ret < 0)
16062306a36Sopenharmony_ci		return ret;
16162306a36Sopenharmony_ci	if (ret == 0)
16262306a36Sopenharmony_ci		cells++;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	if (cells == 0) {
16562306a36Sopenharmony_ci		dev_err(&dev->dev, "All decode registers disabled.\n");
16662306a36Sopenharmony_ci		return -ENODEV;
16762306a36Sopenharmony_ci	}
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	return mfd_add_devices(&dev->dev, 0, lpc_sch_cells, cells, NULL, 0, NULL);
17062306a36Sopenharmony_ci}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_cistatic void lpc_sch_remove(struct pci_dev *dev)
17362306a36Sopenharmony_ci{
17462306a36Sopenharmony_ci	mfd_remove_devices(&dev->dev);
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_cistatic struct pci_driver lpc_sch_driver = {
17862306a36Sopenharmony_ci	.name		= "lpc_sch",
17962306a36Sopenharmony_ci	.id_table	= lpc_sch_ids,
18062306a36Sopenharmony_ci	.probe		= lpc_sch_probe,
18162306a36Sopenharmony_ci	.remove		= lpc_sch_remove,
18262306a36Sopenharmony_ci};
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_cimodule_pci_driver(lpc_sch_driver);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ciMODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>");
18762306a36Sopenharmony_ciMODULE_DESCRIPTION("LPC interface for Intel Poulsbo SCH");
18862306a36Sopenharmony_ciMODULE_LICENSE("GPL");
189