162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Dummy driver for Intel's Image Signal Processor found on Bay Trail
462306a36Sopenharmony_ci * and Cherry Trail devices. The sole purpose of this driver is to allow
562306a36Sopenharmony_ci * the ISP to be put in D3.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Copyright (C) 2018 Hans de Goede <hdegoede@redhat.com>
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * Based on various non upstream patches for ISP support:
1062306a36Sopenharmony_ci * Copyright (C) 2010-2017 Intel Corporation. All rights reserved.
1162306a36Sopenharmony_ci * Copyright (c) 2010 Silicon Hive www.siliconhive.com.
1262306a36Sopenharmony_ci */
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <linux/delay.h>
1562306a36Sopenharmony_ci#include <linux/module.h>
1662306a36Sopenharmony_ci#include <linux/mod_devicetable.h>
1762306a36Sopenharmony_ci#include <linux/pci.h>
1862306a36Sopenharmony_ci#include <linux/pm_runtime.h>
1962306a36Sopenharmony_ci#include <asm/iosf_mbi.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/* PCI configuration regs */
2262306a36Sopenharmony_ci#define PCI_INTERRUPT_CTRL		0x9c
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#define PCI_CSI_CONTROL			0xe8
2562306a36Sopenharmony_ci#define PCI_CSI_CONTROL_PORTS_OFF_MASK	0x7
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci/* IOSF BT_MBI_UNIT_PMC regs */
2862306a36Sopenharmony_ci#define ISPSSPM0			0x39
2962306a36Sopenharmony_ci#define ISPSSPM0_ISPSSC_OFFSET		0
3062306a36Sopenharmony_ci#define ISPSSPM0_ISPSSC_MASK		0x00000003
3162306a36Sopenharmony_ci#define ISPSSPM0_ISPSSS_OFFSET		24
3262306a36Sopenharmony_ci#define ISPSSPM0_ISPSSS_MASK		0x03000000
3362306a36Sopenharmony_ci#define ISPSSPM0_IUNIT_POWER_ON		0x0
3462306a36Sopenharmony_ci#define ISPSSPM0_IUNIT_POWER_OFF	0x3
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic int isp_set_power(struct pci_dev *dev, bool enable)
3762306a36Sopenharmony_ci{
3862306a36Sopenharmony_ci	unsigned long timeout;
3962306a36Sopenharmony_ci	u32 val = enable ? ISPSSPM0_IUNIT_POWER_ON : ISPSSPM0_IUNIT_POWER_OFF;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	/* Write to ISPSSPM0 bit[1:0] to power on/off the IUNIT */
4262306a36Sopenharmony_ci	iosf_mbi_modify(BT_MBI_UNIT_PMC, MBI_REG_READ, ISPSSPM0,
4362306a36Sopenharmony_ci			val, ISPSSPM0_ISPSSC_MASK);
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	/*
4662306a36Sopenharmony_ci	 * There should be no IUNIT access while power-down is
4762306a36Sopenharmony_ci	 * in progress. HW sighting: 4567865.
4862306a36Sopenharmony_ci	 * Wait up to 50 ms for the IUNIT to shut down.
4962306a36Sopenharmony_ci	 * And we do the same for power on.
5062306a36Sopenharmony_ci	 */
5162306a36Sopenharmony_ci	timeout = jiffies + msecs_to_jiffies(50);
5262306a36Sopenharmony_ci	do {
5362306a36Sopenharmony_ci		u32 tmp;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci		/* Wait until ISPSSPM0 bit[25:24] shows the right value */
5662306a36Sopenharmony_ci		iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, ISPSSPM0, &tmp);
5762306a36Sopenharmony_ci		tmp = (tmp & ISPSSPM0_ISPSSS_MASK) >> ISPSSPM0_ISPSSS_OFFSET;
5862306a36Sopenharmony_ci		if (tmp == val)
5962306a36Sopenharmony_ci			return 0;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci		usleep_range(1000, 2000);
6262306a36Sopenharmony_ci	} while (time_before(jiffies, timeout));
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	dev_err(&dev->dev, "IUNIT power-%s timeout.\n", enable ? "on" : "off");
6562306a36Sopenharmony_ci	return -EBUSY;
6662306a36Sopenharmony_ci}
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistatic int isp_probe(struct pci_dev *dev, const struct pci_device_id *id)
6962306a36Sopenharmony_ci{
7062306a36Sopenharmony_ci	pm_runtime_allow(&dev->dev);
7162306a36Sopenharmony_ci	pm_runtime_put_sync_suspend(&dev->dev);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	return 0;
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_cistatic void isp_remove(struct pci_dev *dev)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	pm_runtime_get_sync(&dev->dev);
7962306a36Sopenharmony_ci	pm_runtime_forbid(&dev->dev);
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic int isp_pci_suspend(struct device *dev)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	struct pci_dev *pdev = to_pci_dev(dev);
8562306a36Sopenharmony_ci	u32 val;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	pci_write_config_dword(pdev, PCI_INTERRUPT_CTRL, 0);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	/*
9062306a36Sopenharmony_ci	 * MRFLD IUNIT DPHY is located in an always-power-on island
9162306a36Sopenharmony_ci	 * MRFLD HW design need all CSI ports are disabled before
9262306a36Sopenharmony_ci	 * powering down the IUNIT.
9362306a36Sopenharmony_ci	 */
9462306a36Sopenharmony_ci	pci_read_config_dword(pdev, PCI_CSI_CONTROL, &val);
9562306a36Sopenharmony_ci	val |= PCI_CSI_CONTROL_PORTS_OFF_MASK;
9662306a36Sopenharmony_ci	pci_write_config_dword(pdev, PCI_CSI_CONTROL, val);
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	/*
9962306a36Sopenharmony_ci	 * We lose config space access when punit power gates
10062306a36Sopenharmony_ci	 * the ISP. Can't use pci_set_power_state() because
10162306a36Sopenharmony_ci	 * pmcsr won't actually change when we write to it.
10262306a36Sopenharmony_ci	 */
10362306a36Sopenharmony_ci	pci_save_state(pdev);
10462306a36Sopenharmony_ci	pdev->current_state = PCI_D3cold;
10562306a36Sopenharmony_ci	isp_set_power(pdev, false);
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	return 0;
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic int isp_pci_resume(struct device *dev)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	struct pci_dev *pdev = to_pci_dev(dev);
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	isp_set_power(pdev, true);
11562306a36Sopenharmony_ci	pdev->current_state = PCI_D0;
11662306a36Sopenharmony_ci	pci_restore_state(pdev);
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	return 0;
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_cistatic UNIVERSAL_DEV_PM_OPS(isp_pm_ops, isp_pci_suspend,
12262306a36Sopenharmony_ci			    isp_pci_resume, NULL);
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic const struct pci_device_id isp_id_table[] = {
12562306a36Sopenharmony_ci	{ PCI_VDEVICE(INTEL, 0x0f38), },
12662306a36Sopenharmony_ci	{ PCI_VDEVICE(INTEL, 0x22b8), },
12762306a36Sopenharmony_ci	{ 0, }
12862306a36Sopenharmony_ci};
12962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, isp_id_table);
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_cistatic struct pci_driver isp_pci_driver = {
13262306a36Sopenharmony_ci	.name = "intel_atomisp2_pm",
13362306a36Sopenharmony_ci	.id_table = isp_id_table,
13462306a36Sopenharmony_ci	.probe = isp_probe,
13562306a36Sopenharmony_ci	.remove = isp_remove,
13662306a36Sopenharmony_ci	.driver.pm = &isp_pm_ops,
13762306a36Sopenharmony_ci};
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cimodule_pci_driver(isp_pci_driver);
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ciMODULE_DESCRIPTION("Intel AtomISP2 dummy / power-management drv (for suspend)");
14262306a36Sopenharmony_ciMODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
14362306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
144