2
0
mirror of https://github.com/edk2-porting/linux-next.git synced 2024-12-16 17:23:55 +08:00

Immutable branch between MFD, X86, USB and Watchdog due for the v5.7 merge window

-----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEdrbJNaO+IJqU8IdIUa+KL4f8d2EFAl6iviIACgkQUa+KL4f8
 d2EdiA/+OSpr4fiy9RTIxXnyROoFmI9ypKyDbXdkW0c2E2ZlH6d3LCFRCiF39DeQ
 7U6uNXyNetUjzH6fXm1BM/nltNbAaAUmNlL1THGQ3wamMayL4S0hSHgy8ZUTcFj4
 yC4TLwHAsXmNjHC0FbUul9LILooBs6MfEHeN+zK7tCI4ZLaygx5d/+ApFlbuU45c
 J4u7a32PEmM9i6BvSAQK2OGWChIRGvCnxekz9C8ebWULzzsOKPLFJccKEdORc8dK
 5Mg3J8zC1IZ+ct6y+DvvbaYROP3vbNabJPHIjV/hLmP0g3qHghHUW265qLVrHu/z
 uCek8lgLTYetJ+Nn4/k7eoUDKkZgbgfukRLyHw/gu4YQaiwhjI2KVP+ytBWkCzA4
 AOJVnMN756C1Rx3XrF/E63RdwuIfJA+VGGW7YzbPuXa0SvECPE//wVpZv6FSkJmA
 N4s1Z7yhuZEjGvuu4l5/ErZSbSN/2OMM4ahdQ4xoCAxYKkKMacS5Gds83VlnM6LQ
 Mwl77aTc84JqI1y4HROd2qj4J8YfE1F8lTRt8lepgeOL/kntM9U1lvBT3TCV3T0e
 xLC6GDCHo6N9vlyn1KU4PCyCHFNUJLD6Wq+H/jLZkcrx4k59caR0+I1+iyaQyo/i
 o+DgYCp6GnPFd3XuVLppg45n6qZ7wB67WOJ6iOpcO7+kMldg4Ck=
 =4M2c
 -----END PGP SIGNATURE-----

Merge branch 'ib-mfd-x86-usb-watchdog-v5.7'

Merge branch 'ib-mfd-x86-usb-watchdog-v5.7' of
git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git
to avoid conflicts in PDx86.

Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
This commit is contained in:
Andy Shevchenko 2020-04-24 13:56:46 +03:00
commit 4a65ed6562
31 changed files with 1414 additions and 1323 deletions

View File

@ -0,0 +1,22 @@
These files allow sending arbitrary IPC commands to the PMC/SCU which
may be dangerous. These will be removed eventually and should not be
used in any new applications.
What: /sys/bus/platform/devices/INT34D2:00/simplecmd
Date: Jun 2015
KernelVersion: 4.1
Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
Description: This interface allows userspace to send an arbitrary
IPC command to the PMC/SCU.
Format: %d %d where first number is command and
second number is subcommand.
What: /sys/bus/platform/devices/INT34D2:00/northpeak
Date: Jun 2015
KernelVersion: 4.1
Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
Description: This interface allows userspace to enable and disable
Northpeak through the PMC/SCU.
Format: %u.

View File

@ -8499,6 +8499,13 @@ L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/intel_atomisp2_pm.c
INTEL BROXTON PMC DRIVER
M: Mika Westerberg <mika.westerberg@linux.intel.com>
M: Zha Qipeng <qipeng.zha@intel.com>
S: Maintained
F: drivers/mfd/intel_pmc_bxt.c
F: include/linux/mfd/intel_pmc_bxt.h
INTEL C600 SERIES SAS CONTROLLER DRIVER
M: Intel SCU Linux support <intel-linux-scu@intel.com>
M: Artur Paszkiewicz <artur.paszkiewicz@intel.com>
@ -8706,6 +8713,13 @@ F: include/uapi/linux/mic_common.h
F: include/uapi/linux/mic_ioctl.h
F: include/uapi/linux/scif_ioctl.h
INTEL P-Unit IPC DRIVER
M: Zha Qipeng <qipeng.zha@intel.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: arch/x86/include/asm/intel_punit_ipc.h
F: drivers/platform/x86/intel_punit_ipc.c
INTEL PMC CORE DRIVER
M: Rajneesh Bhardwaj <rajneesh.bhardwaj@intel.com>
M: Vishwanath Somayaji <vishwanath.somayaji@intel.com>
@ -8713,15 +8727,6 @@ L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/intel_pmc_core*
INTEL PMC/P-Unit IPC DRIVER
M: Zha Qipeng<qipeng.zha@intel.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: arch/x86/include/asm/intel_pmc_ipc.h
F: arch/x86/include/asm/intel_punit_ipc.h
F: drivers/platform/x86/intel_pmc_ipc.c
F: drivers/platform/x86/intel_punit_ipc.c
INTEL PMIC GPIO DRIVERS
M: Andy Shevchenko <andy@kernel.org>
S: Maintained

View File

@ -595,7 +595,7 @@ config X86_INTEL_MID
select I2C
select DW_APB_TIMER
select APB_TIMER
select INTEL_SCU_IPC
select INTEL_SCU_PCI
select MFD_INTEL_MSIC
---help---
Select to build a kernel capable of supporting Intel MID (Mobile

View File

@ -88,11 +88,17 @@ static inline bool intel_mid_has_msic(void)
return (intel_mid_identify_cpu() == INTEL_MID_CPU_CHIP_PENWELL);
}
extern void intel_scu_devices_create(void);
extern void intel_scu_devices_destroy(void);
#else /* !CONFIG_X86_INTEL_MID */
#define intel_mid_identify_cpu() 0
#define intel_mid_has_msic() 0
static inline void intel_scu_devices_create(void) { }
static inline void intel_scu_devices_destroy(void) { }
#endif /* !CONFIG_X86_INTEL_MID */
enum intel_mid_timer_options {
@ -115,9 +121,6 @@ extern enum intel_mid_timer_options intel_mid_timer_options;
#define SFI_MTMR_MAX_NUM 8
#define SFI_MRTC_MAX 8
extern void intel_scu_devices_create(void);
extern void intel_scu_devices_destroy(void);
/* VRTC timer */
#define MRST_VRTC_MAP_SZ 1024
/* #define MRST_VRTC_PGOFFSET 0xc00 */

View File

@ -1,59 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _ASM_X86_INTEL_PMC_IPC_H_
#define _ASM_X86_INTEL_PMC_IPC_H_
/* Commands */
#define PMC_IPC_PMIC_ACCESS 0xFF
#define PMC_IPC_PMIC_ACCESS_READ 0x0
#define PMC_IPC_PMIC_ACCESS_WRITE 0x1
#define PMC_IPC_USB_PWR_CTRL 0xF0
#define PMC_IPC_PMIC_BLACKLIST_SEL 0xEF
#define PMC_IPC_PHY_CONFIG 0xEE
#define PMC_IPC_NORTHPEAK_CTRL 0xED
#define PMC_IPC_PM_DEBUG 0xEC
#define PMC_IPC_PMC_TELEMTRY 0xEB
#define PMC_IPC_PMC_FW_MSG_CTRL 0xEA
/* IPC return code */
#define IPC_ERR_NONE 0
#define IPC_ERR_CMD_NOT_SUPPORTED 1
#define IPC_ERR_CMD_NOT_SERVICED 2
#define IPC_ERR_UNABLE_TO_SERVICE 3
#define IPC_ERR_CMD_INVALID 4
#define IPC_ERR_CMD_FAILED 5
#define IPC_ERR_EMSECURITY 6
#define IPC_ERR_UNSIGNEDKERNEL 7
/* GCR reg offsets from gcr base*/
#define PMC_GCR_PMC_CFG_REG 0x08
#define PMC_GCR_TELEM_DEEP_S0IX_REG 0x78
#define PMC_GCR_TELEM_SHLW_S0IX_REG 0x80
#if IS_ENABLED(CONFIG_INTEL_PMC_IPC)
int intel_pmc_ipc_command(u32 cmd, u32 sub, u8 *in, u32 inlen,
u32 *out, u32 outlen);
int intel_pmc_s0ix_counter_read(u64 *data);
int intel_pmc_gcr_read64(u32 offset, u64 *data);
#else
static inline int intel_pmc_ipc_command(u32 cmd, u32 sub, u8 *in, u32 inlen,
u32 *out, u32 outlen)
{
return -EINVAL;
}
static inline int intel_pmc_s0ix_counter_read(u64 *data)
{
return -EINVAL;
}
static inline int intel_pmc_gcr_read64(u32 offset, u64 *data)
{
return -EINVAL;
}
#endif /*CONFIG_INTEL_PMC_IPC*/
#endif

View File

@ -2,61 +2,69 @@
#ifndef _ASM_X86_INTEL_SCU_IPC_H_
#define _ASM_X86_INTEL_SCU_IPC_H_
#include <linux/notifier.h>
#include <linux/ioport.h>
#define IPCMSG_INDIRECT_READ 0x02
#define IPCMSG_INDIRECT_WRITE 0x05
struct device;
struct intel_scu_ipc_dev;
#define IPCMSG_COLD_OFF 0x80 /* Only for Tangier */
/**
* struct intel_scu_ipc_data - Data used to configure SCU IPC
* @mem: Base address of SCU IPC MMIO registers
* @irq: The IRQ number used for SCU (optional)
*/
struct intel_scu_ipc_data {
struct resource mem;
int irq;
};
#define IPCMSG_WARM_RESET 0xF0
#define IPCMSG_COLD_RESET 0xF1
#define IPCMSG_SOFT_RESET 0xF2
#define IPCMSG_COLD_BOOT 0xF3
struct intel_scu_ipc_dev *
__intel_scu_ipc_register(struct device *parent,
const struct intel_scu_ipc_data *scu_data,
struct module *owner);
#define IPCMSG_VRTC 0xFA /* Set vRTC device */
/* Command id associated with message IPCMSG_VRTC */
#define IPC_CMD_VRTC_SETTIME 1 /* Set time */
#define IPC_CMD_VRTC_SETALARM 2 /* Set alarm */
#define intel_scu_ipc_register(parent, scu_data) \
__intel_scu_ipc_register(parent, scu_data, THIS_MODULE)
/* Read single register */
int intel_scu_ipc_ioread8(u16 addr, u8 *data);
void intel_scu_ipc_unregister(struct intel_scu_ipc_dev *scu);
/* Read a vector */
int intel_scu_ipc_readv(u16 *addr, u8 *data, int len);
struct intel_scu_ipc_dev *
__devm_intel_scu_ipc_register(struct device *parent,
const struct intel_scu_ipc_data *scu_data,
struct module *owner);
/* Write single register */
int intel_scu_ipc_iowrite8(u16 addr, u8 data);
#define devm_intel_scu_ipc_register(parent, scu_data) \
__devm_intel_scu_ipc_register(parent, scu_data, THIS_MODULE)
/* Write a vector */
int intel_scu_ipc_writev(u16 *addr, u8 *data, int len);
struct intel_scu_ipc_dev *intel_scu_ipc_dev_get(void);
void intel_scu_ipc_dev_put(struct intel_scu_ipc_dev *scu);
struct intel_scu_ipc_dev *devm_intel_scu_ipc_dev_get(struct device *dev);
/* Update single register based on the mask */
int intel_scu_ipc_update_register(u16 addr, u8 data, u8 mask);
int intel_scu_ipc_dev_ioread8(struct intel_scu_ipc_dev *scu, u16 addr,
u8 *data);
int intel_scu_ipc_dev_iowrite8(struct intel_scu_ipc_dev *scu, u16 addr,
u8 data);
int intel_scu_ipc_dev_readv(struct intel_scu_ipc_dev *scu, u16 *addr,
u8 *data, size_t len);
int intel_scu_ipc_dev_writev(struct intel_scu_ipc_dev *scu, u16 *addr,
u8 *data, size_t len);
/* Issue commands to the SCU with or without data */
int intel_scu_ipc_simple_command(int cmd, int sub);
int intel_scu_ipc_command(int cmd, int sub, u32 *in, int inlen,
u32 *out, int outlen);
int intel_scu_ipc_dev_update(struct intel_scu_ipc_dev *scu, u16 addr,
u8 data, u8 mask);
extern struct blocking_notifier_head intel_scu_notifier;
int intel_scu_ipc_dev_simple_command(struct intel_scu_ipc_dev *scu, int cmd,
int sub);
int intel_scu_ipc_dev_command_with_size(struct intel_scu_ipc_dev *scu, int cmd,
int sub, const void *in, size_t inlen,
size_t size, void *out, size_t outlen);
static inline void intel_scu_notifier_add(struct notifier_block *nb)
static inline int intel_scu_ipc_dev_command(struct intel_scu_ipc_dev *scu, int cmd,
int sub, const void *in, size_t inlen,
void *out, size_t outlen)
{
blocking_notifier_chain_register(&intel_scu_notifier, nb);
return intel_scu_ipc_dev_command_with_size(scu, cmd, sub, in, inlen,
inlen, out, outlen);
}
static inline void intel_scu_notifier_remove(struct notifier_block *nb)
{
blocking_notifier_chain_unregister(&intel_scu_notifier, nb);
}
static inline int intel_scu_notifier_post(unsigned long v, void *p)
{
return blocking_notifier_call_chain(&intel_scu_notifier, v, p);
}
#define SCU_AVAILABLE 1
#define SCU_DOWN 2
#include <asm/intel_scu_ipc_legacy.h>
#endif

View File

@ -0,0 +1,91 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _ASM_X86_INTEL_SCU_IPC_LEGACY_H_
#define _ASM_X86_INTEL_SCU_IPC_LEGACY_H_
#include <linux/notifier.h>
#define IPCMSG_INDIRECT_READ 0x02
#define IPCMSG_INDIRECT_WRITE 0x05
#define IPCMSG_COLD_OFF 0x80 /* Only for Tangier */
#define IPCMSG_WARM_RESET 0xF0
#define IPCMSG_COLD_RESET 0xF1
#define IPCMSG_SOFT_RESET 0xF2
#define IPCMSG_COLD_BOOT 0xF3
#define IPCMSG_VRTC 0xFA /* Set vRTC device */
/* Command id associated with message IPCMSG_VRTC */
#define IPC_CMD_VRTC_SETTIME 1 /* Set time */
#define IPC_CMD_VRTC_SETALARM 2 /* Set alarm */
/* Don't call these in new code - they will be removed eventually */
/* Read single register */
static inline int intel_scu_ipc_ioread8(u16 addr, u8 *data)
{
return intel_scu_ipc_dev_ioread8(NULL, addr, data);
}
/* Read a vector */
static inline int intel_scu_ipc_readv(u16 *addr, u8 *data, int len)
{
return intel_scu_ipc_dev_readv(NULL, addr, data, len);
}
/* Write single register */
static inline int intel_scu_ipc_iowrite8(u16 addr, u8 data)
{
return intel_scu_ipc_dev_iowrite8(NULL, addr, data);
}
/* Write a vector */
static inline int intel_scu_ipc_writev(u16 *addr, u8 *data, int len)
{
return intel_scu_ipc_dev_writev(NULL, addr, data, len);
}
/* Update single register based on the mask */
static inline int intel_scu_ipc_update_register(u16 addr, u8 data, u8 mask)
{
return intel_scu_ipc_dev_update(NULL, addr, data, mask);
}
/* Issue commands to the SCU with or without data */
static inline int intel_scu_ipc_simple_command(int cmd, int sub)
{
return intel_scu_ipc_dev_simple_command(NULL, cmd, sub);
}
static inline int intel_scu_ipc_command(int cmd, int sub, u32 *in, int inlen,
u32 *out, int outlen)
{
/* New API takes both inlen and outlen as bytes so convert here */
size_t inbytes = inlen * sizeof(u32);
size_t outbytes = outlen * sizeof(u32);
return intel_scu_ipc_dev_command_with_size(NULL, cmd, sub, in, inbytes,
inlen, out, outbytes);
}
extern struct blocking_notifier_head intel_scu_notifier;
static inline void intel_scu_notifier_add(struct notifier_block *nb)
{
blocking_notifier_chain_register(&intel_scu_notifier, nb);
}
static inline void intel_scu_notifier_remove(struct notifier_block *nb)
{
blocking_notifier_chain_unregister(&intel_scu_notifier, nb);
}
static inline int intel_scu_notifier_post(unsigned long v, void *p)
{
return blocking_notifier_call_chain(&intel_scu_notifier, v, p);
}
#define SCU_AVAILABLE 1
#define SCU_DOWN 2
#endif

View File

@ -10,6 +10,8 @@
#define TELEM_MAX_EVENTS_SRAM 28
#define TELEM_MAX_OS_ALLOCATED_EVENTS 20
#include <asm/intel_scu_ipc.h>
enum telemetry_unit {
TELEM_PSS = 0,
TELEM_IOSS,
@ -51,6 +53,8 @@ struct telemetry_plt_config {
struct telemetry_unit_config ioss_config;
struct mutex telem_trace_lock;
struct mutex telem_lock;
struct intel_pmc_dev *pmc;
struct intel_scu_ipc_dev *scu;
bool telem_in_use;
};
@ -92,7 +96,7 @@ int telemetry_set_pltdata(const struct telemetry_core_ops *ops,
int telemetry_clear_pltdata(void);
int telemetry_pltconfig_valid(void);
struct telemetry_plt_config *telemetry_get_pltdata(void);
int telemetry_get_evtname(enum telemetry_unit telem_unit,
const char **name, int len);

View File

@ -551,7 +551,7 @@ config INTEL_SOC_PMIC
config INTEL_SOC_PMIC_BXTWC
tristate "Support for Intel Broxton Whiskey Cove PMIC"
depends on INTEL_PMC_IPC
depends on MFD_INTEL_PMC_BXT
select MFD_CORE
select REGMAP_IRQ
help
@ -593,7 +593,7 @@ config INTEL_SOC_PMIC_MRFLD
tristate "Support for Intel Merrifield Basin Cove PMIC"
depends on GPIOLIB
depends on ACPI
depends on INTEL_SCU_IPC
depends on INTEL_SCU
select MFD_CORE
select REGMAP_IRQ
help
@ -625,13 +625,27 @@ config MFD_INTEL_LPSS_PCI
config MFD_INTEL_MSIC
bool "Intel MSIC"
depends on INTEL_SCU_IPC
depends on INTEL_SCU
select MFD_CORE
help
Select this option to enable access to Intel MSIC (Avatele
Passage) chip. This chip embeds audio, battery, GPIO, etc.
devices used in Intel Medfield platforms.
config MFD_INTEL_PMC_BXT
tristate "Intel PMC Driver for Broxton"
depends on X86
depends on X86_PLATFORM_DEVICES
depends on ACPI
select INTEL_SCU_IPC
select MFD_CORE
help
This driver provides support for the PMC (Power Management
Controller) on Intel Broxton and Apollo Lake. The PMC is a
multi-function device that exposes IPC, General Control
Register and P-unit access. In addition this creates devices
for iTCO watchdog and telemetry that are part of the PMC.
config MFD_IPAQ_MICRO
bool "Atmel Micro ASIC (iPAQ h3100/h3600/h3700) Support"
depends on SA1100_H3100 || SA1100_H3600

View File

@ -212,6 +212,7 @@ obj-$(CONFIG_MFD_INTEL_LPSS) += intel-lpss.o
obj-$(CONFIG_MFD_INTEL_LPSS_PCI) += intel-lpss-pci.o
obj-$(CONFIG_MFD_INTEL_LPSS_ACPI) += intel-lpss-acpi.o
obj-$(CONFIG_MFD_INTEL_MSIC) += intel_msic.o
obj-$(CONFIG_MFD_INTEL_PMC_BXT) += intel_pmc_bxt.o
obj-$(CONFIG_MFD_PALMAS) += palmas.o
obj-$(CONFIG_MFD_VIPERBOARD) += viperboard.o
obj-$(CONFIG_MFD_RC5T583) += rc5t583.o rc5t583-irq.o

468
drivers/mfd/intel_pmc_bxt.c Normal file
View File

@ -0,0 +1,468 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for the Intel Broxton PMC
*
* (C) Copyright 2014 - 2020 Intel Corporation
*
* This driver is based on Intel SCU IPC driver (intel_scu_ipc.c) by
* Sreedhara DS <sreedhara.ds@intel.com>
*
* The PMC (Power Management Controller) running on the ARC processor
* communicates with another entity running in the IA (Intel Architecture)
* core through an IPC (Intel Processor Communications) mechanism which in
* turn sends messages between the IA and the PMC.
*/
#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/mfd/core.h>
#include <linux/mfd/intel_pmc_bxt.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/platform_data/itco_wdt.h>
#include <asm/intel_scu_ipc.h>
/* Residency with clock rate at 19.2MHz to usecs */
#define S0IX_RESIDENCY_IN_USECS(d, s) \
({ \
u64 result = 10ull * ((d) + (s)); \
do_div(result, 192); \
result; \
})
/* Resources exported from IFWI */
#define PLAT_RESOURCE_IPC_INDEX 0
#define PLAT_RESOURCE_IPC_SIZE 0x1000
#define PLAT_RESOURCE_GCR_OFFSET 0x1000
#define PLAT_RESOURCE_GCR_SIZE 0x1000
#define PLAT_RESOURCE_BIOS_DATA_INDEX 1
#define PLAT_RESOURCE_BIOS_IFACE_INDEX 2
#define PLAT_RESOURCE_TELEM_SSRAM_INDEX 3
#define PLAT_RESOURCE_ISP_DATA_INDEX 4
#define PLAT_RESOURCE_ISP_IFACE_INDEX 5
#define PLAT_RESOURCE_GTD_DATA_INDEX 6
#define PLAT_RESOURCE_GTD_IFACE_INDEX 7
#define PLAT_RESOURCE_ACPI_IO_INDEX 0
/*
* BIOS does not create an ACPI device for each PMC function, but
* exports multiple resources from one ACPI device (IPC) for multiple
* functions. This driver is responsible for creating a child device and
* to export resources for those functions.
*/
#define SMI_EN_OFFSET 0x0040
#define SMI_EN_SIZE 4
#define TCO_BASE_OFFSET 0x0060
#define TCO_REGS_SIZE 16
#define TELEM_SSRAM_SIZE 240
#define TELEM_PMC_SSRAM_OFFSET 0x1b00
#define TELEM_PUNIT_SSRAM_OFFSET 0x1a00
/* Commands */
#define PMC_NORTHPEAK_CTRL 0xed
static inline bool is_gcr_valid(u32 offset)
{
return offset < PLAT_RESOURCE_GCR_SIZE - 8;
}
/**
* intel_pmc_gcr_read64() - Read a 64-bit PMC GCR register
* @pmc: PMC device pointer
* @offset: offset of GCR register from GCR address base
* @data: data pointer for storing the register output
*
* Reads the 64-bit PMC GCR register at given offset.
*
* Return: Negative value on error or 0 on success.
*/
int intel_pmc_gcr_read64(struct intel_pmc_dev *pmc, u32 offset, u64 *data)
{
if (!is_gcr_valid(offset))
return -EINVAL;
spin_lock(&pmc->gcr_lock);
*data = readq(pmc->gcr_mem_base + offset);
spin_unlock(&pmc->gcr_lock);
return 0;
}
EXPORT_SYMBOL_GPL(intel_pmc_gcr_read64);
/**
* intel_pmc_gcr_update() - Update PMC GCR register bits
* @pmc: PMC device pointer
* @offset: offset of GCR register from GCR address base
* @mask: bit mask for update operation
* @val: update value
*
* Updates the bits of given GCR register as specified by
* @mask and @val.
*
* Return: Negative value on error or 0 on success.
*/
int intel_pmc_gcr_update(struct intel_pmc_dev *pmc, u32 offset, u32 mask, u32 val)
{
u32 new_val;
if (!is_gcr_valid(offset))
return -EINVAL;
spin_lock(&pmc->gcr_lock);
new_val = readl(pmc->gcr_mem_base + offset);
new_val = (new_val & ~mask) | (val & mask);
writel(new_val, pmc->gcr_mem_base + offset);
new_val = readl(pmc->gcr_mem_base + offset);
spin_unlock(&pmc->gcr_lock);
/* Check whether the bit update is successful */
return (new_val & mask) != (val & mask) ? -EIO : 0;
}
EXPORT_SYMBOL_GPL(intel_pmc_gcr_update);
/**
* intel_pmc_s0ix_counter_read() - Read S0ix residency
* @pmc: PMC device pointer
* @data: Out param that contains current S0ix residency count.
*
* Writes to @data how many usecs the system has been in low-power S0ix
* state.
*
* Return: An error code or 0 on success.
*/
int intel_pmc_s0ix_counter_read(struct intel_pmc_dev *pmc, u64 *data)
{
u64 deep, shlw;
spin_lock(&pmc->gcr_lock);
deep = readq(pmc->gcr_mem_base + PMC_GCR_TELEM_DEEP_S0IX_REG);
shlw = readq(pmc->gcr_mem_base + PMC_GCR_TELEM_SHLW_S0IX_REG);
spin_unlock(&pmc->gcr_lock);
*data = S0IX_RESIDENCY_IN_USECS(deep, shlw);
return 0;
}
EXPORT_SYMBOL_GPL(intel_pmc_s0ix_counter_read);
/**
* simplecmd_store() - Send a simple IPC command
* @dev: Device under the attribute is
* @attr: Attribute in question
* @buf: Buffer holding data to be stored to the attribute
* @count: Number of bytes in @buf
*
* Expects a string with two integers separated with space. These two
* values hold command and subcommand that is send to PMC.
*
* Return: Number number of bytes written (@count) or negative errno in
* case of error.
*/
static ssize_t simplecmd_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct intel_pmc_dev *pmc = dev_get_drvdata(dev);
struct intel_scu_ipc_dev *scu = pmc->scu;
int subcmd;
int cmd;
int ret;
ret = sscanf(buf, "%d %d", &cmd, &subcmd);
if (ret != 2) {
dev_err(dev, "Invalid values, expected: cmd subcmd\n");
return -EINVAL;
}
ret = intel_scu_ipc_dev_simple_command(scu, cmd, subcmd);
if (ret)
return ret;
return count;
}
static DEVICE_ATTR_WO(simplecmd);
/**
* northpeak_store() - Enable or disable Northpeak
* @dev: Device under the attribute is
* @attr: Attribute in question
* @buf: Buffer holding data to be stored to the attribute
* @count: Number of bytes in @buf
*
* Expects an unsigned integer. Non-zero enables Northpeak and zero
* disables it.
*
* Return: Number number of bytes written (@count) or negative errno in
* case of error.
*/
static ssize_t northpeak_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct intel_pmc_dev *pmc = dev_get_drvdata(dev);
struct intel_scu_ipc_dev *scu = pmc->scu;
unsigned long val;
int subcmd;
int ret;
ret = kstrtoul(buf, 0, &val);
if (ret)
return ret;
/* Northpeak is enabled if subcmd == 1 and disabled if it is 0 */
if (val)
subcmd = 1;
else
subcmd = 0;
ret = intel_scu_ipc_dev_simple_command(scu, PMC_NORTHPEAK_CTRL, subcmd);
if (ret)
return ret;
return count;
}
static DEVICE_ATTR_WO(northpeak);
static struct attribute *intel_pmc_attrs[] = {
&dev_attr_northpeak.attr,
&dev_attr_simplecmd.attr,
NULL
};
static const struct attribute_group intel_pmc_group = {
.attrs = intel_pmc_attrs,
};
static const struct attribute_group *intel_pmc_groups[] = {
&intel_pmc_group,
NULL
};
static struct resource punit_res[6];
static struct mfd_cell punit = {
.name = "intel_punit_ipc",
.resources = punit_res,
};
static struct itco_wdt_platform_data tco_pdata = {
.name = "Apollo Lake SoC",
.version = 5,
.no_reboot_use_pmc = true,
};
static struct resource tco_res[2];
static const struct mfd_cell tco = {
.name = "iTCO_wdt",
.ignore_resource_conflicts = true,
.resources = tco_res,
.num_resources = ARRAY_SIZE(tco_res),
.platform_data = &tco_pdata,
.pdata_size = sizeof(tco_pdata),
};
static const struct resource telem_res[] = {
DEFINE_RES_MEM(TELEM_PUNIT_SSRAM_OFFSET, TELEM_SSRAM_SIZE),
DEFINE_RES_MEM(TELEM_PMC_SSRAM_OFFSET, TELEM_SSRAM_SIZE),
};
static const struct mfd_cell telem = {
.name = "intel_telemetry",
.resources = telem_res,
.num_resources = ARRAY_SIZE(telem_res),
};
static int intel_pmc_get_tco_resources(struct platform_device *pdev)
{
struct resource *res;
if (acpi_has_watchdog())
return 0;
res = platform_get_resource(pdev, IORESOURCE_IO,
PLAT_RESOURCE_ACPI_IO_INDEX);
if (!res) {
dev_err(&pdev->dev, "Failed to get IO resource\n");
return -EINVAL;
}
tco_res[0].flags = IORESOURCE_IO;
tco_res[0].start = res->start + TCO_BASE_OFFSET;
tco_res[0].end = tco_res[0].start + TCO_REGS_SIZE - 1;
tco_res[1].flags = IORESOURCE_IO;
tco_res[1].start = res->start + SMI_EN_OFFSET;
tco_res[1].end = tco_res[1].start + SMI_EN_SIZE - 1;
return 0;
}
static int intel_pmc_get_resources(struct platform_device *pdev,
struct intel_pmc_dev *pmc,
struct intel_scu_ipc_data *scu_data)
{
struct resource gcr_res;
size_t npunit_res = 0;
struct resource *res;
int ret;
scu_data->irq = platform_get_irq_optional(pdev, 0);
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_IPC_INDEX);
if (!res) {
dev_err(&pdev->dev, "Failed to get IPC resource\n");
return -EINVAL;
}
/* IPC registers */
scu_data->mem.flags = res->flags;
scu_data->mem.start = res->start;
scu_data->mem.end = res->start + PLAT_RESOURCE_IPC_SIZE - 1;
/* GCR registers */
gcr_res.flags = res->flags;
gcr_res.start = res->start + PLAT_RESOURCE_GCR_OFFSET;
gcr_res.end = gcr_res.start + PLAT_RESOURCE_GCR_SIZE - 1;
pmc->gcr_mem_base = devm_ioremap_resource(&pdev->dev, &gcr_res);
if (IS_ERR(pmc->gcr_mem_base))
return PTR_ERR(pmc->gcr_mem_base);
/* Only register iTCO watchdog if there is no WDAT ACPI table */
ret = intel_pmc_get_tco_resources(pdev);
if (ret)
return ret;
/* BIOS data register */
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_BIOS_DATA_INDEX);
if (!res) {
dev_err(&pdev->dev, "Failed to get resource of P-unit BIOS data\n");
return -EINVAL;
}
punit_res[npunit_res++] = *res;
/* BIOS interface register */
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_BIOS_IFACE_INDEX);
if (!res) {
dev_err(&pdev->dev, "Failed to get resource of P-unit BIOS interface\n");
return -EINVAL;
}
punit_res[npunit_res++] = *res;
/* ISP data register, optional */
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_ISP_DATA_INDEX);
if (res)
punit_res[npunit_res++] = *res;
/* ISP interface register, optional */
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_ISP_IFACE_INDEX);
if (res)
punit_res[npunit_res++] = *res;
/* GTD data register, optional */
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_GTD_DATA_INDEX);
if (res)
punit_res[npunit_res++] = *res;
/* GTD interface register, optional */
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_GTD_IFACE_INDEX);
if (res)
punit_res[npunit_res++] = *res;
punit.num_resources = npunit_res;
/* Telemetry SSRAM is optional */
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_TELEM_SSRAM_INDEX);
if (res)
pmc->telem_base = res;
return 0;
}
static int intel_pmc_create_devices(struct intel_pmc_dev *pmc)
{
int ret;
if (!acpi_has_watchdog()) {
ret = devm_mfd_add_devices(pmc->dev, PLATFORM_DEVID_AUTO, &tco,
1, NULL, 0, NULL);
if (ret)
return ret;
}
ret = devm_mfd_add_devices(pmc->dev, PLATFORM_DEVID_AUTO, &punit, 1,
NULL, 0, NULL);
if (ret)
return ret;
if (pmc->telem_base) {
ret = devm_mfd_add_devices(pmc->dev, PLATFORM_DEVID_AUTO,
&telem, 1, pmc->telem_base, 0, NULL);
}
return ret;
}
static const struct acpi_device_id intel_pmc_acpi_ids[] = {
{ "INT34D2" },
{ }
};
MODULE_DEVICE_TABLE(acpi, intel_pmc_acpi_ids);
static int intel_pmc_probe(struct platform_device *pdev)
{
struct intel_scu_ipc_data scu_data = {};
struct intel_pmc_dev *pmc;
int ret;
pmc = devm_kzalloc(&pdev->dev, sizeof(*pmc), GFP_KERNEL);
if (!pmc)
return -ENOMEM;
pmc->dev = &pdev->dev;
spin_lock_init(&pmc->gcr_lock);
ret = intel_pmc_get_resources(pdev, pmc, &scu_data);
if (ret) {
dev_err(&pdev->dev, "Failed to request resources\n");
return ret;
}
pmc->scu = devm_intel_scu_ipc_register(&pdev->dev, &scu_data);
if (IS_ERR(pmc->scu))
return PTR_ERR(pmc->scu);
platform_set_drvdata(pdev, pmc);
ret = intel_pmc_create_devices(pmc);
if (ret)
dev_err(&pdev->dev, "Failed to create PMC devices\n");
return ret;
}
static struct platform_driver intel_pmc_driver = {
.probe = intel_pmc_probe,
.driver = {
.name = "intel_pmc_bxt",
.acpi_match_table = intel_pmc_acpi_ids,
.dev_groups = intel_pmc_groups,
},
};
module_platform_driver(intel_pmc_driver);
MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
MODULE_AUTHOR("Zha Qipeng <qipeng.zha@intel.com>");
MODULE_DESCRIPTION("Intel Broxton PMC driver");
MODULE_LICENSE("GPL v2");

View File

@ -15,7 +15,7 @@
#include <linux/mfd/intel_soc_pmic_bxtwc.h>
#include <linux/module.h>
#include <asm/intel_pmc_ipc.h>
#include <asm/intel_scu_ipc.h>
/* PMIC device registers */
#define REG_ADDR_MASK 0xFF00
@ -58,6 +58,10 @@
/* Whiskey Cove PMIC share same ACPI ID between different platforms */
#define BROXTON_PMIC_WC_HRV 4
#define PMC_PMIC_ACCESS 0xFF
#define PMC_PMIC_READ 0x0
#define PMC_PMIC_WRITE 0x1
enum bxtwc_irqs {
BXTWC_PWRBTN_LVL1_IRQ = 0,
BXTWC_TMU_LVL1_IRQ,
@ -288,13 +292,12 @@ static int regmap_ipc_byte_reg_read(void *context, unsigned int reg,
ipc_in[0] = reg;
ipc_in[1] = i2c_addr;
ret = intel_pmc_ipc_command(PMC_IPC_PMIC_ACCESS,
PMC_IPC_PMIC_ACCESS_READ,
ipc_in, sizeof(ipc_in), (u32 *)ipc_out, 1);
if (ret) {
dev_err(pmic->dev, "Failed to read from PMIC\n");
ret = intel_scu_ipc_dev_command(pmic->scu, PMC_PMIC_ACCESS,
PMC_PMIC_READ, ipc_in, sizeof(ipc_in),
ipc_out, sizeof(ipc_out));
if (ret)
return ret;
}
*val = ipc_out[0];
return 0;
@ -303,7 +306,6 @@ static int regmap_ipc_byte_reg_read(void *context, unsigned int reg,
static int regmap_ipc_byte_reg_write(void *context, unsigned int reg,
unsigned int val)
{
int ret;
int i2c_addr;
u8 ipc_in[3];
struct intel_soc_pmic *pmic = context;
@ -321,15 +323,9 @@ static int regmap_ipc_byte_reg_write(void *context, unsigned int reg,
ipc_in[0] = reg;
ipc_in[1] = i2c_addr;
ipc_in[2] = val;
ret = intel_pmc_ipc_command(PMC_IPC_PMIC_ACCESS,
PMC_IPC_PMIC_ACCESS_WRITE,
ipc_in, sizeof(ipc_in), NULL, 0);
if (ret) {
dev_err(pmic->dev, "Failed to write to PMIC\n");
return ret;
}
return 0;
return intel_scu_ipc_dev_command(pmic->scu, PMC_PMIC_ACCESS,
PMC_PMIC_WRITE, ipc_in, sizeof(ipc_in),
NULL, 0);
}
/* sysfs interfaces to r/w PMIC registers, required by initial script */
@ -457,6 +453,10 @@ static int bxtwc_probe(struct platform_device *pdev)
dev_set_drvdata(&pdev->dev, pmic);
pmic->dev = &pdev->dev;
pmic->scu = devm_intel_scu_ipc_dev_get(&pdev->dev);
if (!pmic->scu)
return -EPROBE_DEFER;
pmic->regmap = devm_regmap_init(&pdev->dev, NULL, pmic,
&bxtwc_regmap_config);
if (IS_ERR(pmic->regmap)) {

View File

@ -74,10 +74,11 @@ static const struct mfd_cell bcove_dev[] = {
static int bcove_ipc_byte_reg_read(void *context, unsigned int reg,
unsigned int *val)
{
struct intel_soc_pmic *pmic = context;
u8 ipc_out;
int ret;
ret = intel_scu_ipc_ioread8(reg, &ipc_out);
ret = intel_scu_ipc_dev_ioread8(pmic->scu, reg, &ipc_out);
if (ret)
return ret;
@ -88,10 +89,11 @@ static int bcove_ipc_byte_reg_read(void *context, unsigned int reg,
static int bcove_ipc_byte_reg_write(void *context, unsigned int reg,
unsigned int val)
{
struct intel_soc_pmic *pmic = context;
u8 ipc_in = val;
int ret;
ret = intel_scu_ipc_iowrite8(reg, ipc_in);
ret = intel_scu_ipc_dev_iowrite8(pmic->scu, reg, ipc_in);
if (ret)
return ret;
@ -117,6 +119,10 @@ static int bcove_probe(struct platform_device *pdev)
if (!pmic)
return -ENOMEM;
pmic->scu = devm_intel_scu_ipc_dev_get(dev);
if (!pmic->scu)
return -ENOMEM;
platform_set_drvdata(pdev, pmic);
pmic->dev = &pdev->dev;

View File

@ -1269,7 +1269,8 @@ config INTEL_UNCORE_FREQ_CONTROL
config INTEL_BXTWC_PMIC_TMU
tristate "Intel BXT Whiskey Cove TMU Driver"
depends on REGMAP
depends on INTEL_SOC_PMIC_BXTWC && INTEL_PMC_IPC
depends on MFD_INTEL_PMC_BXT
depends on INTEL_SOC_PMIC_BXTWC
---help---
Select this driver to use Intel BXT Whiskey Cove PMIC TMU feature.
This driver enables the alarm wakeup functionality in the TMU unit
@ -1295,7 +1296,7 @@ config INTEL_MFLD_THERMAL
config INTEL_MID_POWER_BUTTON
tristate "power button driver for Intel MID platforms"
depends on INTEL_SCU_IPC && INPUT
depends on INTEL_SCU && INPUT
help
This driver handles the power button on the Intel MID platforms.
@ -1327,14 +1328,6 @@ config INTEL_PMC_CORE
- LTR Ignore
- MPHY/PLL gating status (Sunrisepoint PCH only)
config INTEL_PMC_IPC
tristate "Intel PMC IPC Driver"
depends on ACPI && PCI
---help---
This driver provides support for PMC control on some Intel platforms.
The PMC is an ARC processor which defines IPC commands for communication
with other entities in the CPU.
config INTEL_PUNIT_IPC
tristate "Intel P-Unit IPC Driver"
---help---
@ -1342,17 +1335,30 @@ config INTEL_PUNIT_IPC
which is used to bridge the communications between kernel and P-Unit.
config INTEL_SCU_IPC
bool "Intel SCU IPC Support"
depends on X86_INTEL_MID
default y
---help---
IPC is used to bridge the communications between kernel and SCU on
some embedded Intel x86 platforms. This is not needed for PC-type
machines.
bool
config INTEL_SCU
bool
select INTEL_SCU_IPC
config INTEL_SCU_PCI
bool "Intel SCU PCI driver"
depends on PCI
select INTEL_SCU
help
This driver is used to bridge the communications between kernel
and SCU on some embedded Intel x86 platforms. It also creates
devices that are connected to the SoC through the SCU.
Platforms supported:
Medfield
Clovertrail
Merrifield
Broxton
Apollo Lake
config INTEL_SCU_IPC_UTIL
tristate "Intel SCU IPC utility driver"
depends on INTEL_SCU_IPC
depends on INTEL_SCU
---help---
The IPC Util driver provides an interface with the SCU enabling
low level access for debug work and updating the firmware. Say
@ -1360,7 +1366,9 @@ config INTEL_SCU_IPC_UTIL
config INTEL_TELEMETRY
tristate "Intel SoC Telemetry Driver"
depends on INTEL_PMC_IPC && INTEL_PUNIT_IPC && X86_64
depends on X86_64
depends on MFD_INTEL_PMC_BXT
depends on INTEL_PUNIT_IPC
---help---
This driver provides interfaces to configure and use
telemetry for INTEL SoC from APL onwards. It is also

View File

@ -138,9 +138,9 @@ obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o
obj-$(CONFIG_INTEL_MID_POWER_BUTTON) += intel_mid_powerbtn.o
obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o
obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o
obj-$(CONFIG_INTEL_PMC_IPC) += intel_pmc_ipc.o
obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o
obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o
obj-$(CONFIG_INTEL_SCU_PCI) += intel_scu_pcidrv.o
obj-$(CONFIG_INTEL_SCU_IPC_UTIL) += intel_scu_ipcutil.o
obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \
intel_telemetry_pltdrv.o \

View File

@ -46,6 +46,7 @@ struct mid_pb_ddata {
unsigned short mirqlvl1_addr;
unsigned short pbstat_addr;
u8 pbstat_mask;
struct intel_scu_ipc_dev *scu;
int (*setup)(struct mid_pb_ddata *ddata);
};
@ -55,7 +56,8 @@ static int mid_pbstat(struct mid_pb_ddata *ddata, int *value)
int ret;
u8 pbstat;
ret = intel_scu_ipc_ioread8(ddata->pbstat_addr, &pbstat);
ret = intel_scu_ipc_dev_ioread8(ddata->scu, ddata->pbstat_addr,
&pbstat);
if (ret)
return ret;
@ -67,14 +69,15 @@ static int mid_pbstat(struct mid_pb_ddata *ddata, int *value)
static int mid_irq_ack(struct mid_pb_ddata *ddata)
{
return intel_scu_ipc_update_register(ddata->mirqlvl1_addr, 0, MSIC_PWRBTNM);
return intel_scu_ipc_dev_update(ddata->scu, ddata->mirqlvl1_addr, 0,
MSIC_PWRBTNM);
}
static int mrfld_setup(struct mid_pb_ddata *ddata)
{
/* Unmask the PBIRQ and MPBIRQ on Tangier */
intel_scu_ipc_update_register(BCOVE_PBIRQ, 0, MSIC_PWRBTNM);
intel_scu_ipc_update_register(BCOVE_PBIRQMASK, 0, MSIC_PWRBTNM);
intel_scu_ipc_dev_update(ddata->scu, BCOVE_PBIRQ, 0, MSIC_PWRBTNM);
intel_scu_ipc_dev_update(ddata->scu, BCOVE_PBIRQMASK, 0, MSIC_PWRBTNM);
return 0;
}
@ -161,6 +164,10 @@ static int mid_pb_probe(struct platform_device *pdev)
return error;
}
ddata->scu = devm_intel_scu_ipc_dev_get(&pdev->dev);
if (!ddata->scu)
return -EPROBE_DEFER;
error = devm_request_threaded_irq(&pdev->dev, irq, NULL, mid_pb_isr,
IRQF_ONESHOT, DRIVER_NAME, ddata);
if (error) {

View File

@ -1,949 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for the Intel PMC IPC mechanism
*
* (C) Copyright 2014-2015 Intel Corporation
*
* This driver is based on Intel SCU IPC driver(intel_scu_ipc.c) by
* Sreedhara DS <sreedhara.ds@intel.com>
*
* PMC running in ARC processor communicates with other entity running in IA
* core through IPC mechanism which in turn messaging between IA core ad PMC.
*/
#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <asm/intel_pmc_ipc.h>
#include <linux/platform_data/itco_wdt.h>
/*
* IPC registers
* The IA write to IPC_CMD command register triggers an interrupt to the ARC,
* The ARC handles the interrupt and services it, writing optional data to
* the IPC1 registers, updates the IPC_STS response register with the status.
*/
#define IPC_CMD 0x00
#define IPC_CMD_MSI BIT(8)
#define IPC_CMD_SIZE 16
#define IPC_CMD_SUBCMD 12
#define IPC_STATUS 0x04
#define IPC_STATUS_IRQ BIT(2)
#define IPC_STATUS_ERR BIT(1)
#define IPC_STATUS_BUSY BIT(0)
#define IPC_SPTR 0x08
#define IPC_DPTR 0x0C
#define IPC_WRITE_BUFFER 0x80
#define IPC_READ_BUFFER 0x90
/* Residency with clock rate at 19.2MHz to usecs */
#define S0IX_RESIDENCY_IN_USECS(d, s) \
({ \
u64 result = 10ull * ((d) + (s)); \
do_div(result, 192); \
result; \
})
/*
* 16-byte buffer for sending data associated with IPC command.
*/
#define IPC_DATA_BUFFER_SIZE 16
#define IPC_LOOP_CNT 3000000
#define IPC_MAX_SEC 3
#define IPC_TRIGGER_MODE_IRQ true
/* exported resources from IFWI */
#define PLAT_RESOURCE_IPC_INDEX 0
#define PLAT_RESOURCE_IPC_SIZE 0x1000
#define PLAT_RESOURCE_GCR_OFFSET 0x1000
#define PLAT_RESOURCE_GCR_SIZE 0x1000
#define PLAT_RESOURCE_BIOS_DATA_INDEX 1
#define PLAT_RESOURCE_BIOS_IFACE_INDEX 2
#define PLAT_RESOURCE_TELEM_SSRAM_INDEX 3
#define PLAT_RESOURCE_ISP_DATA_INDEX 4
#define PLAT_RESOURCE_ISP_IFACE_INDEX 5
#define PLAT_RESOURCE_GTD_DATA_INDEX 6
#define PLAT_RESOURCE_GTD_IFACE_INDEX 7
#define PLAT_RESOURCE_ACPI_IO_INDEX 0
/*
* BIOS does not create an ACPI device for each PMC function,
* but exports multiple resources from one ACPI device(IPC) for
* multiple functions. This driver is responsible to create a
* platform device and to export resources for those functions.
*/
#define TCO_DEVICE_NAME "iTCO_wdt"
#define SMI_EN_OFFSET 0x40
#define SMI_EN_SIZE 4
#define TCO_BASE_OFFSET 0x60
#define TCO_REGS_SIZE 16
#define PUNIT_DEVICE_NAME "intel_punit_ipc"
#define TELEMETRY_DEVICE_NAME "intel_telemetry"
#define TELEM_SSRAM_SIZE 240
#define TELEM_PMC_SSRAM_OFFSET 0x1B00
#define TELEM_PUNIT_SSRAM_OFFSET 0x1A00
#define TCO_PMC_OFFSET 0x08
#define TCO_PMC_SIZE 0x04
/* PMC register bit definitions */
/* PMC_CFG_REG bit masks */
#define PMC_CFG_NO_REBOOT_MASK BIT_MASK(4)
#define PMC_CFG_NO_REBOOT_EN (1 << 4)
#define PMC_CFG_NO_REBOOT_DIS (0 << 4)
static struct intel_pmc_ipc_dev {
struct device *dev;
void __iomem *ipc_base;
bool irq_mode;
int irq;
int cmd;
struct completion cmd_complete;
/* The following PMC BARs share the same ACPI device with the IPC */
resource_size_t acpi_io_base;
int acpi_io_size;
struct platform_device *tco_dev;
/* gcr */
void __iomem *gcr_mem_base;
bool has_gcr_regs;
spinlock_t gcr_lock;
/* punit */
struct platform_device *punit_dev;
unsigned int punit_res_count;
/* Telemetry */
resource_size_t telem_pmc_ssram_base;
resource_size_t telem_punit_ssram_base;
int telem_pmc_ssram_size;
int telem_punit_ssram_size;
u8 telem_res_inval;
struct platform_device *telemetry_dev;
} ipcdev;
static char *ipc_err_sources[] = {
[IPC_ERR_NONE] =
"no error",
[IPC_ERR_CMD_NOT_SUPPORTED] =
"command not supported",
[IPC_ERR_CMD_NOT_SERVICED] =
"command not serviced",
[IPC_ERR_UNABLE_TO_SERVICE] =
"unable to service",
[IPC_ERR_CMD_INVALID] =
"command invalid",
[IPC_ERR_CMD_FAILED] =
"command failed",
[IPC_ERR_EMSECURITY] =
"Invalid Battery",
[IPC_ERR_UNSIGNEDKERNEL] =
"Unsigned kernel",
};
/* Prevent concurrent calls to the PMC */
static DEFINE_MUTEX(ipclock);
static inline void ipc_send_command(u32 cmd)
{
ipcdev.cmd = cmd;
if (ipcdev.irq_mode) {
reinit_completion(&ipcdev.cmd_complete);
cmd |= IPC_CMD_MSI;
}
writel(cmd, ipcdev.ipc_base + IPC_CMD);
}
static inline u32 ipc_read_status(void)
{
return readl(ipcdev.ipc_base + IPC_STATUS);
}
static inline void ipc_data_writel(u32 data, u32 offset)
{
writel(data, ipcdev.ipc_base + IPC_WRITE_BUFFER + offset);
}
static inline u32 ipc_data_readl(u32 offset)
{
return readl(ipcdev.ipc_base + IPC_READ_BUFFER + offset);
}
static inline u64 gcr_data_readq(u32 offset)
{
return readq(ipcdev.gcr_mem_base + offset);
}
static inline int is_gcr_valid(u32 offset)
{
if (!ipcdev.has_gcr_regs)
return -EACCES;
if (offset > PLAT_RESOURCE_GCR_SIZE)
return -EINVAL;
return 0;
}
/**
* intel_pmc_gcr_read64() - Read a 64-bit PMC GCR register
* @offset: offset of GCR register from GCR address base
* @data: data pointer for storing the register output
*
* Reads the 64-bit PMC GCR register at given offset.
*
* Return: negative value on error or 0 on success.
*/
int intel_pmc_gcr_read64(u32 offset, u64 *data)
{
int ret;
spin_lock(&ipcdev.gcr_lock);
ret = is_gcr_valid(offset);
if (ret < 0) {
spin_unlock(&ipcdev.gcr_lock);
return ret;
}
*data = readq(ipcdev.gcr_mem_base + offset);
spin_unlock(&ipcdev.gcr_lock);
return 0;
}
EXPORT_SYMBOL_GPL(intel_pmc_gcr_read64);
/**
* intel_pmc_gcr_update() - Update PMC GCR register bits
* @offset: offset of GCR register from GCR address base
* @mask: bit mask for update operation
* @val: update value
*
* Updates the bits of given GCR register as specified by
* @mask and @val.
*
* Return: negative value on error or 0 on success.
*/
static int intel_pmc_gcr_update(u32 offset, u32 mask, u32 val)
{
u32 new_val;
int ret = 0;
spin_lock(&ipcdev.gcr_lock);
ret = is_gcr_valid(offset);
if (ret < 0)
goto gcr_ipc_unlock;
new_val = readl(ipcdev.gcr_mem_base + offset);
new_val &= ~mask;
new_val |= val & mask;
writel(new_val, ipcdev.gcr_mem_base + offset);
new_val = readl(ipcdev.gcr_mem_base + offset);
/* check whether the bit update is successful */
if ((new_val & mask) != (val & mask)) {
ret = -EIO;
goto gcr_ipc_unlock;
}
gcr_ipc_unlock:
spin_unlock(&ipcdev.gcr_lock);
return ret;
}
static int update_no_reboot_bit(void *priv, bool set)
{
u32 value = set ? PMC_CFG_NO_REBOOT_EN : PMC_CFG_NO_REBOOT_DIS;
return intel_pmc_gcr_update(PMC_GCR_PMC_CFG_REG,
PMC_CFG_NO_REBOOT_MASK, value);
}
static int intel_pmc_ipc_check_status(void)
{
int status;
int ret = 0;
if (ipcdev.irq_mode) {
if (0 == wait_for_completion_timeout(
&ipcdev.cmd_complete, IPC_MAX_SEC * HZ))
ret = -ETIMEDOUT;
} else {
int loop_count = IPC_LOOP_CNT;
while ((ipc_read_status() & IPC_STATUS_BUSY) && --loop_count)
udelay(1);
if (loop_count == 0)
ret = -ETIMEDOUT;
}
status = ipc_read_status();
if (ret == -ETIMEDOUT) {
dev_err(ipcdev.dev,
"IPC timed out, TS=0x%x, CMD=0x%x\n",
status, ipcdev.cmd);
return ret;
}
if (status & IPC_STATUS_ERR) {
int i;
ret = -EIO;
i = (status >> IPC_CMD_SIZE) & 0xFF;
if (i < ARRAY_SIZE(ipc_err_sources))
dev_err(ipcdev.dev,
"IPC failed: %s, STS=0x%x, CMD=0x%x\n",
ipc_err_sources[i], status, ipcdev.cmd);
else
dev_err(ipcdev.dev,
"IPC failed: unknown, STS=0x%x, CMD=0x%x\n",
status, ipcdev.cmd);
if ((i == IPC_ERR_UNSIGNEDKERNEL) || (i == IPC_ERR_EMSECURITY))
ret = -EACCES;
}
return ret;
}
/**
* intel_pmc_ipc_simple_command() - Simple IPC command
* @cmd: IPC command code.
* @sub: IPC command sub type.
*
* Send a simple IPC command to PMC when don't need to specify
* input/output data and source/dest pointers.
*
* Return: an IPC error code or 0 on success.
*/
static int intel_pmc_ipc_simple_command(int cmd, int sub)
{
int ret;
mutex_lock(&ipclock);
if (ipcdev.dev == NULL) {
mutex_unlock(&ipclock);
return -ENODEV;
}
ipc_send_command(sub << IPC_CMD_SUBCMD | cmd);
ret = intel_pmc_ipc_check_status();
mutex_unlock(&ipclock);
return ret;
}
/**
* intel_pmc_ipc_raw_cmd() - IPC command with data and pointers
* @cmd: IPC command code.
* @sub: IPC command sub type.
* @in: input data of this IPC command.
* @inlen: input data length in bytes.
* @out: output data of this IPC command.
* @outlen: output data length in dwords.
* @sptr: data writing to SPTR register.
* @dptr: data writing to DPTR register.
*
* Send an IPC command to PMC with input/output data and source/dest pointers.
*
* Return: an IPC error code or 0 on success.
*/
static int intel_pmc_ipc_raw_cmd(u32 cmd, u32 sub, u8 *in, u32 inlen, u32 *out,
u32 outlen, u32 dptr, u32 sptr)
{
u32 wbuf[4] = { 0 };
int ret;
int i;
if (inlen > IPC_DATA_BUFFER_SIZE || outlen > IPC_DATA_BUFFER_SIZE / 4)
return -EINVAL;
mutex_lock(&ipclock);
if (ipcdev.dev == NULL) {
mutex_unlock(&ipclock);
return -ENODEV;
}
memcpy(wbuf, in, inlen);
writel(dptr, ipcdev.ipc_base + IPC_DPTR);
writel(sptr, ipcdev.ipc_base + IPC_SPTR);
/* The input data register is 32bit register and inlen is in Byte */
for (i = 0; i < ((inlen + 3) / 4); i++)
ipc_data_writel(wbuf[i], 4 * i);
ipc_send_command((inlen << IPC_CMD_SIZE) |
(sub << IPC_CMD_SUBCMD) | cmd);
ret = intel_pmc_ipc_check_status();
if (!ret) {
/* out is read from 32bit register and outlen is in 32bit */
for (i = 0; i < outlen; i++)
*out++ = ipc_data_readl(4 * i);
}
mutex_unlock(&ipclock);
return ret;
}
/**
* intel_pmc_ipc_command() - IPC command with input/output data
* @cmd: IPC command code.
* @sub: IPC command sub type.
* @in: input data of this IPC command.
* @inlen: input data length in bytes.
* @out: output data of this IPC command.
* @outlen: output data length in dwords.
*
* Send an IPC command to PMC with input/output data.
*
* Return: an IPC error code or 0 on success.
*/
int intel_pmc_ipc_command(u32 cmd, u32 sub, u8 *in, u32 inlen,
u32 *out, u32 outlen)
{
return intel_pmc_ipc_raw_cmd(cmd, sub, in, inlen, out, outlen, 0, 0);
}
EXPORT_SYMBOL_GPL(intel_pmc_ipc_command);
static irqreturn_t ioc(int irq, void *dev_id)
{
int status;
if (ipcdev.irq_mode) {
status = ipc_read_status();
writel(status | IPC_STATUS_IRQ, ipcdev.ipc_base + IPC_STATUS);
}
complete(&ipcdev.cmd_complete);
return IRQ_HANDLED;
}
static int ipc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct intel_pmc_ipc_dev *pmc = &ipcdev;
int ret;
/* Only one PMC is supported */
if (pmc->dev)
return -EBUSY;
pmc->irq_mode = IPC_TRIGGER_MODE_IRQ;
spin_lock_init(&ipcdev.gcr_lock);
ret = pcim_enable_device(pdev);
if (ret)
return ret;
ret = pcim_iomap_regions(pdev, 1 << 0, pci_name(pdev));
if (ret)
return ret;
init_completion(&pmc->cmd_complete);
pmc->ipc_base = pcim_iomap_table(pdev)[0];
ret = devm_request_irq(&pdev->dev, pdev->irq, ioc, 0, "intel_pmc_ipc",
pmc);
if (ret) {
dev_err(&pdev->dev, "Failed to request irq\n");
return ret;
}
pmc->dev = &pdev->dev;
pci_set_drvdata(pdev, pmc);
return 0;
}
static const struct pci_device_id ipc_pci_ids[] = {
{PCI_VDEVICE(INTEL, 0x0a94), 0},
{PCI_VDEVICE(INTEL, 0x1a94), 0},
{PCI_VDEVICE(INTEL, 0x5a94), 0},
{ 0,}
};
MODULE_DEVICE_TABLE(pci, ipc_pci_ids);
static struct pci_driver ipc_pci_driver = {
.name = "intel_pmc_ipc",
.id_table = ipc_pci_ids,
.probe = ipc_pci_probe,
};
static ssize_t intel_pmc_ipc_simple_cmd_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int subcmd;
int cmd;
int ret;
ret = sscanf(buf, "%d %d", &cmd, &subcmd);
if (ret != 2) {
dev_err(dev, "Error args\n");
return -EINVAL;
}
ret = intel_pmc_ipc_simple_command(cmd, subcmd);
if (ret) {
dev_err(dev, "command %d error with %d\n", cmd, ret);
return ret;
}
return (ssize_t)count;
}
static DEVICE_ATTR(simplecmd, 0200, NULL, intel_pmc_ipc_simple_cmd_store);
static ssize_t intel_pmc_ipc_northpeak_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long val;
int subcmd;
int ret;
ret = kstrtoul(buf, 0, &val);
if (ret)
return ret;
if (val)
subcmd = 1;
else
subcmd = 0;
ret = intel_pmc_ipc_simple_command(PMC_IPC_NORTHPEAK_CTRL, subcmd);
if (ret) {
dev_err(dev, "command north %d error with %d\n", subcmd, ret);
return ret;
}
return (ssize_t)count;
}
static DEVICE_ATTR(northpeak, 0200, NULL, intel_pmc_ipc_northpeak_store);
static struct attribute *intel_ipc_attrs[] = {
&dev_attr_northpeak.attr,
&dev_attr_simplecmd.attr,
NULL
};
static const struct attribute_group intel_ipc_group = {
.attrs = intel_ipc_attrs,
};
static const struct attribute_group *intel_ipc_groups[] = {
&intel_ipc_group,
NULL
};
static struct resource punit_res_array[] = {
/* Punit BIOS */
{
.flags = IORESOURCE_MEM,
},
{
.flags = IORESOURCE_MEM,
},
/* Punit ISP */
{
.flags = IORESOURCE_MEM,
},
{
.flags = IORESOURCE_MEM,
},
/* Punit GTD */
{
.flags = IORESOURCE_MEM,
},
{
.flags = IORESOURCE_MEM,
},
};
#define TCO_RESOURCE_ACPI_IO 0
#define TCO_RESOURCE_SMI_EN_IO 1
#define TCO_RESOURCE_GCR_MEM 2
static struct resource tco_res[] = {
/* ACPI - TCO */
{
.flags = IORESOURCE_IO,
},
/* ACPI - SMI */
{
.flags = IORESOURCE_IO,
},
};
static struct itco_wdt_platform_data tco_info = {
.name = "Apollo Lake SoC",
.version = 5,
.no_reboot_priv = &ipcdev,
.update_no_reboot_bit = update_no_reboot_bit,
};
#define TELEMETRY_RESOURCE_PUNIT_SSRAM 0
#define TELEMETRY_RESOURCE_PMC_SSRAM 1
static struct resource telemetry_res[] = {
/*Telemetry*/
{
.flags = IORESOURCE_MEM,
},
{
.flags = IORESOURCE_MEM,
},
};
static int ipc_create_punit_device(void)
{
struct platform_device *pdev;
const struct platform_device_info pdevinfo = {
.parent = ipcdev.dev,
.name = PUNIT_DEVICE_NAME,
.id = -1,
.res = punit_res_array,
.num_res = ipcdev.punit_res_count,
};
pdev = platform_device_register_full(&pdevinfo);
if (IS_ERR(pdev))
return PTR_ERR(pdev);
ipcdev.punit_dev = pdev;
return 0;
}
static int ipc_create_tco_device(void)
{
struct platform_device *pdev;
struct resource *res;
const struct platform_device_info pdevinfo = {
.parent = ipcdev.dev,
.name = TCO_DEVICE_NAME,
.id = -1,
.res = tco_res,
.num_res = ARRAY_SIZE(tco_res),
.data = &tco_info,
.size_data = sizeof(tco_info),
};
res = tco_res + TCO_RESOURCE_ACPI_IO;
res->start = ipcdev.acpi_io_base + TCO_BASE_OFFSET;
res->end = res->start + TCO_REGS_SIZE - 1;
res = tco_res + TCO_RESOURCE_SMI_EN_IO;
res->start = ipcdev.acpi_io_base + SMI_EN_OFFSET;
res->end = res->start + SMI_EN_SIZE - 1;
pdev = platform_device_register_full(&pdevinfo);
if (IS_ERR(pdev))
return PTR_ERR(pdev);
ipcdev.tco_dev = pdev;
return 0;
}
static int ipc_create_telemetry_device(void)
{
struct platform_device *pdev;
struct resource *res;
const struct platform_device_info pdevinfo = {
.parent = ipcdev.dev,
.name = TELEMETRY_DEVICE_NAME,
.id = -1,
.res = telemetry_res,
.num_res = ARRAY_SIZE(telemetry_res),
};
res = telemetry_res + TELEMETRY_RESOURCE_PUNIT_SSRAM;
res->start = ipcdev.telem_punit_ssram_base;
res->end = res->start + ipcdev.telem_punit_ssram_size - 1;
res = telemetry_res + TELEMETRY_RESOURCE_PMC_SSRAM;
res->start = ipcdev.telem_pmc_ssram_base;
res->end = res->start + ipcdev.telem_pmc_ssram_size - 1;
pdev = platform_device_register_full(&pdevinfo);
if (IS_ERR(pdev))
return PTR_ERR(pdev);
ipcdev.telemetry_dev = pdev;
return 0;
}
static int ipc_create_pmc_devices(void)
{
int ret;
/* If we have ACPI based watchdog use that instead */
if (!acpi_has_watchdog()) {
ret = ipc_create_tco_device();
if (ret) {
dev_err(ipcdev.dev, "Failed to add tco platform device\n");
return ret;
}
}
ret = ipc_create_punit_device();
if (ret) {
dev_err(ipcdev.dev, "Failed to add punit platform device\n");
platform_device_unregister(ipcdev.tco_dev);
return ret;
}
if (!ipcdev.telem_res_inval) {
ret = ipc_create_telemetry_device();
if (ret) {
dev_warn(ipcdev.dev,
"Failed to add telemetry platform device\n");
platform_device_unregister(ipcdev.punit_dev);
platform_device_unregister(ipcdev.tco_dev);
}
}
return ret;
}
static int ipc_plat_get_res(struct platform_device *pdev)
{
struct resource *res, *punit_res = punit_res_array;
void __iomem *addr;
int size;
res = platform_get_resource(pdev, IORESOURCE_IO,
PLAT_RESOURCE_ACPI_IO_INDEX);
if (!res) {
dev_err(&pdev->dev, "Failed to get io resource\n");
return -ENXIO;
}
size = resource_size(res);
ipcdev.acpi_io_base = res->start;
ipcdev.acpi_io_size = size;
dev_info(&pdev->dev, "io res: %pR\n", res);
ipcdev.punit_res_count = 0;
/* This is index 0 to cover BIOS data register */
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_BIOS_DATA_INDEX);
if (!res) {
dev_err(&pdev->dev, "Failed to get res of punit BIOS data\n");
return -ENXIO;
}
punit_res[ipcdev.punit_res_count++] = *res;
dev_info(&pdev->dev, "punit BIOS data res: %pR\n", res);
/* This is index 1 to cover BIOS interface register */
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_BIOS_IFACE_INDEX);
if (!res) {
dev_err(&pdev->dev, "Failed to get res of punit BIOS iface\n");
return -ENXIO;
}
punit_res[ipcdev.punit_res_count++] = *res;
dev_info(&pdev->dev, "punit BIOS interface res: %pR\n", res);
/* This is index 2 to cover ISP data register, optional */
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_ISP_DATA_INDEX);
if (res) {
punit_res[ipcdev.punit_res_count++] = *res;
dev_info(&pdev->dev, "punit ISP data res: %pR\n", res);
}
/* This is index 3 to cover ISP interface register, optional */
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_ISP_IFACE_INDEX);
if (res) {
punit_res[ipcdev.punit_res_count++] = *res;
dev_info(&pdev->dev, "punit ISP interface res: %pR\n", res);
}
/* This is index 4 to cover GTD data register, optional */
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_GTD_DATA_INDEX);
if (res) {
punit_res[ipcdev.punit_res_count++] = *res;
dev_info(&pdev->dev, "punit GTD data res: %pR\n", res);
}
/* This is index 5 to cover GTD interface register, optional */
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_GTD_IFACE_INDEX);
if (res) {
punit_res[ipcdev.punit_res_count++] = *res;
dev_info(&pdev->dev, "punit GTD interface res: %pR\n", res);
}
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_IPC_INDEX);
if (!res) {
dev_err(&pdev->dev, "Failed to get ipc resource\n");
return -ENXIO;
}
size = PLAT_RESOURCE_IPC_SIZE + PLAT_RESOURCE_GCR_SIZE;
res->end = res->start + size - 1;
addr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(addr))
return PTR_ERR(addr);
ipcdev.ipc_base = addr;
ipcdev.gcr_mem_base = addr + PLAT_RESOURCE_GCR_OFFSET;
dev_info(&pdev->dev, "ipc res: %pR\n", res);
ipcdev.telem_res_inval = 0;
res = platform_get_resource(pdev, IORESOURCE_MEM,
PLAT_RESOURCE_TELEM_SSRAM_INDEX);
if (!res) {
dev_err(&pdev->dev, "Failed to get telemetry ssram resource\n");
ipcdev.telem_res_inval = 1;
} else {
ipcdev.telem_punit_ssram_base = res->start +
TELEM_PUNIT_SSRAM_OFFSET;
ipcdev.telem_punit_ssram_size = TELEM_SSRAM_SIZE;
ipcdev.telem_pmc_ssram_base = res->start +
TELEM_PMC_SSRAM_OFFSET;
ipcdev.telem_pmc_ssram_size = TELEM_SSRAM_SIZE;
dev_info(&pdev->dev, "telemetry ssram res: %pR\n", res);
}
return 0;
}
/**
* intel_pmc_s0ix_counter_read() - Read S0ix residency.
* @data: Out param that contains current S0ix residency count.
*
* Return: an error code or 0 on success.
*/
int intel_pmc_s0ix_counter_read(u64 *data)
{
u64 deep, shlw;
if (!ipcdev.has_gcr_regs)
return -EACCES;
deep = gcr_data_readq(PMC_GCR_TELEM_DEEP_S0IX_REG);
shlw = gcr_data_readq(PMC_GCR_TELEM_SHLW_S0IX_REG);
*data = S0IX_RESIDENCY_IN_USECS(deep, shlw);
return 0;
}
EXPORT_SYMBOL_GPL(intel_pmc_s0ix_counter_read);
#ifdef CONFIG_ACPI
static const struct acpi_device_id ipc_acpi_ids[] = {
{ "INT34D2", 0},
{ }
};
MODULE_DEVICE_TABLE(acpi, ipc_acpi_ids);
#endif
static int ipc_plat_probe(struct platform_device *pdev)
{
int ret;
ipcdev.dev = &pdev->dev;
ipcdev.irq_mode = IPC_TRIGGER_MODE_IRQ;
init_completion(&ipcdev.cmd_complete);
spin_lock_init(&ipcdev.gcr_lock);
ipcdev.irq = platform_get_irq(pdev, 0);
if (ipcdev.irq < 0)
return -EINVAL;
ret = ipc_plat_get_res(pdev);
if (ret) {
dev_err(&pdev->dev, "Failed to request resource\n");
return ret;
}
ret = ipc_create_pmc_devices();
if (ret) {
dev_err(&pdev->dev, "Failed to create pmc devices\n");
return ret;
}
if (devm_request_irq(&pdev->dev, ipcdev.irq, ioc, IRQF_NO_SUSPEND,
"intel_pmc_ipc", &ipcdev)) {
dev_err(&pdev->dev, "Failed to request irq\n");
ret = -EBUSY;
goto err_irq;
}
ipcdev.has_gcr_regs = true;
return 0;
err_irq:
platform_device_unregister(ipcdev.tco_dev);
platform_device_unregister(ipcdev.punit_dev);
platform_device_unregister(ipcdev.telemetry_dev);
return ret;
}
static int ipc_plat_remove(struct platform_device *pdev)
{
devm_free_irq(&pdev->dev, ipcdev.irq, &ipcdev);
platform_device_unregister(ipcdev.tco_dev);
platform_device_unregister(ipcdev.punit_dev);
platform_device_unregister(ipcdev.telemetry_dev);
ipcdev.dev = NULL;
return 0;
}
static struct platform_driver ipc_plat_driver = {
.remove = ipc_plat_remove,
.probe = ipc_plat_probe,
.driver = {
.name = "pmc-ipc-plat",
.acpi_match_table = ACPI_PTR(ipc_acpi_ids),
.dev_groups = intel_ipc_groups,
},
};
static int __init intel_pmc_ipc_init(void)
{
int ret;
ret = platform_driver_register(&ipc_plat_driver);
if (ret) {
pr_err("Failed to register PMC ipc platform driver\n");
return ret;
}
ret = pci_register_driver(&ipc_pci_driver);
if (ret) {
pr_err("Failed to register PMC ipc pci driver\n");
platform_driver_unregister(&ipc_plat_driver);
return ret;
}
return ret;
}
static void __exit intel_pmc_ipc_exit(void)
{
pci_unregister_driver(&ipc_pci_driver);
platform_driver_unregister(&ipc_plat_driver);
}
MODULE_AUTHOR("Zha Qipeng <qipeng.zha@intel.com>");
MODULE_DESCRIPTION("Intel PMC IPC driver");
MODULE_LICENSE("GPL v2");
/* Some modules are dependent on this, so init earlier */
fs_initcall(intel_pmc_ipc_init);
module_exit(intel_pmc_ipc_exit);

View File

@ -18,11 +18,10 @@
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/pm.h>
#include <linux/sfi.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <asm/intel-mid.h>
#include <asm/intel_scu_ipc.h>
/* IPC defines the following message types */
@ -55,14 +54,14 @@
#define IPC_IOC 0x100 /* IPC command register IOC bit */
struct intel_scu_ipc_dev {
struct device *dev;
struct device dev;
struct resource mem;
struct module *owner;
int irq;
void __iomem *ipc_base;
struct completion cmd_complete;
u8 irq_mode;
};
static struct intel_scu_ipc_dev ipcdev; /* Only one for now */
#define IPC_STATUS 0x04
#define IPC_STATUS_IRQ BIT(2)
#define IPC_STATUS_ERR BIT(1)
@ -78,8 +77,110 @@ static struct intel_scu_ipc_dev ipcdev; /* Only one for now */
/* Timeout in jiffies */
#define IPC_TIMEOUT (3 * HZ)
static struct intel_scu_ipc_dev *ipcdev; /* Only one for now */
static DEFINE_MUTEX(ipclock); /* lock used to prevent multiple call to SCU */
static struct class intel_scu_ipc_class = {
.name = "intel_scu_ipc",
.owner = THIS_MODULE,
};
/**
* intel_scu_ipc_dev_get() - Get SCU IPC instance
*
* The recommended new API takes SCU IPC instance as parameter and this
* function can be called by driver to get the instance. This also makes
* sure the driver providing the IPC functionality cannot be unloaded
* while the caller has the instance.
*
* Call intel_scu_ipc_dev_put() to release the instance.
*
* Returns %NULL if SCU IPC is not currently available.
*/
struct intel_scu_ipc_dev *intel_scu_ipc_dev_get(void)
{
struct intel_scu_ipc_dev *scu = NULL;
mutex_lock(&ipclock);
if (ipcdev) {
get_device(&ipcdev->dev);
/*
* Prevent the IPC provider from being unloaded while it
* is being used.
*/
if (!try_module_get(ipcdev->owner))
put_device(&ipcdev->dev);
else
scu = ipcdev;
}
mutex_unlock(&ipclock);
return scu;
}
EXPORT_SYMBOL_GPL(intel_scu_ipc_dev_get);
/**
* intel_scu_ipc_dev_put() - Put SCU IPC instance
* @scu: SCU IPC instance
*
* This function releases the SCU IPC instance retrieved from
* intel_scu_ipc_dev_get() and allows the driver providing IPC to be
* unloaded.
*/
void intel_scu_ipc_dev_put(struct intel_scu_ipc_dev *scu)
{
if (scu) {
module_put(scu->owner);
put_device(&scu->dev);
}
}
EXPORT_SYMBOL_GPL(intel_scu_ipc_dev_put);
struct intel_scu_ipc_devres {
struct intel_scu_ipc_dev *scu;
};
static void devm_intel_scu_ipc_dev_release(struct device *dev, void *res)
{
struct intel_scu_ipc_devres *dr = res;
struct intel_scu_ipc_dev *scu = dr->scu;
intel_scu_ipc_dev_put(scu);
}
/**
* devm_intel_scu_ipc_dev_get() - Allocate managed SCU IPC device
* @dev: Device requesting the SCU IPC device
*
* The recommended new API takes SCU IPC instance as parameter and this
* function can be called by driver to get the instance. This also makes
* sure the driver providing the IPC functionality cannot be unloaded
* while the caller has the instance.
*
* Returns %NULL if SCU IPC is not currently available.
*/
struct intel_scu_ipc_dev *devm_intel_scu_ipc_dev_get(struct device *dev)
{
struct intel_scu_ipc_devres *dr;
struct intel_scu_ipc_dev *scu;
dr = devres_alloc(devm_intel_scu_ipc_dev_release, sizeof(*dr), GFP_KERNEL);
if (!dr)
return NULL;
scu = intel_scu_ipc_dev_get();
if (!scu) {
devres_free(dr);
return NULL;
}
dr->scu = scu;
devres_add(dev, dr);
return scu;
}
EXPORT_SYMBOL_GPL(devm_intel_scu_ipc_dev_get);
/*
* Send ipc command
* Command Register (Write Only):
@ -143,7 +244,6 @@ static inline int busy_loop(struct intel_scu_ipc_dev *scu)
usleep_range(50, 100);
} while (time_before(jiffies, end));
dev_err(scu->dev, "IPC timed out");
return -ETIMEDOUT;
}
@ -152,10 +252,8 @@ static inline int ipc_wait_for_interrupt(struct intel_scu_ipc_dev *scu)
{
int status;
if (!wait_for_completion_timeout(&scu->cmd_complete, IPC_TIMEOUT)) {
dev_err(scu->dev, "IPC timed out\n");
if (!wait_for_completion_timeout(&scu->cmd_complete, IPC_TIMEOUT))
return -ETIMEDOUT;
}
status = ipc_read_status(scu);
if (status & IPC_STATUS_ERR)
@ -166,13 +264,13 @@ static inline int ipc_wait_for_interrupt(struct intel_scu_ipc_dev *scu)
static int intel_scu_ipc_check_status(struct intel_scu_ipc_dev *scu)
{
return scu->irq_mode ? ipc_wait_for_interrupt(scu) : busy_loop(scu);
return scu->irq > 0 ? ipc_wait_for_interrupt(scu) : busy_loop(scu);
}
/* Read/Write power control(PMIC in Langwell, MSIC in PenWell) registers */
static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id)
static int pwr_reg_rdwr(struct intel_scu_ipc_dev *scu, u16 *addr, u8 *data,
u32 count, u32 op, u32 id)
{
struct intel_scu_ipc_dev *scu = &ipcdev;
int nc;
u32 offset = 0;
int err;
@ -182,8 +280,9 @@ static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id)
memset(cbuf, 0, sizeof(cbuf));
mutex_lock(&ipclock);
if (scu->dev == NULL) {
if (!scu)
scu = ipcdev;
if (!scu) {
mutex_unlock(&ipclock);
return -ENODEV;
}
@ -222,7 +321,8 @@ static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id)
}
/**
* intel_scu_ipc_ioread8 - read a word via the SCU
* intel_scu_ipc_dev_ioread8() - Read a byte via the SCU
* @scu: Optional SCU IPC instance
* @addr: Register on SCU
* @data: Return pointer for read byte
*
@ -231,14 +331,15 @@ static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id)
*
* This function may sleep.
*/
int intel_scu_ipc_ioread8(u16 addr, u8 *data)
int intel_scu_ipc_dev_ioread8(struct intel_scu_ipc_dev *scu, u16 addr, u8 *data)
{
return pwr_reg_rdwr(&addr, data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
return pwr_reg_rdwr(scu, &addr, data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
}
EXPORT_SYMBOL(intel_scu_ipc_ioread8);
EXPORT_SYMBOL(intel_scu_ipc_dev_ioread8);
/**
* intel_scu_ipc_iowrite8 - write a byte via the SCU
* intel_scu_ipc_dev_iowrite8() - Write a byte via the SCU
* @scu: Optional SCU IPC instance
* @addr: Register on SCU
* @data: Byte to write
*
@ -247,14 +348,15 @@ EXPORT_SYMBOL(intel_scu_ipc_ioread8);
*
* This function may sleep.
*/
int intel_scu_ipc_iowrite8(u16 addr, u8 data)
int intel_scu_ipc_dev_iowrite8(struct intel_scu_ipc_dev *scu, u16 addr, u8 data)
{
return pwr_reg_rdwr(&addr, &data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
return pwr_reg_rdwr(scu, &addr, &data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
}
EXPORT_SYMBOL(intel_scu_ipc_iowrite8);
EXPORT_SYMBOL(intel_scu_ipc_dev_iowrite8);
/**
* intel_scu_ipc_readvv - read a set of registers
* intel_scu_ipc_dev_readv() - Read a set of registers
* @scu: Optional SCU IPC instance
* @addr: Register list
* @data: Bytes to return
* @len: Length of array
@ -266,14 +368,16 @@ EXPORT_SYMBOL(intel_scu_ipc_iowrite8);
*
* This function may sleep.
*/
int intel_scu_ipc_readv(u16 *addr, u8 *data, int len)
int intel_scu_ipc_dev_readv(struct intel_scu_ipc_dev *scu, u16 *addr, u8 *data,
size_t len)
{
return pwr_reg_rdwr(addr, data, len, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
return pwr_reg_rdwr(scu, addr, data, len, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
}
EXPORT_SYMBOL(intel_scu_ipc_readv);
EXPORT_SYMBOL(intel_scu_ipc_dev_readv);
/**
* intel_scu_ipc_writev - write a set of registers
* intel_scu_ipc_dev_writev() - Write a set of registers
* @scu: Optional SCU IPC instance
* @addr: Register list
* @data: Bytes to write
* @len: Length of array
@ -285,16 +389,18 @@ EXPORT_SYMBOL(intel_scu_ipc_readv);
*
* This function may sleep.
*/
int intel_scu_ipc_writev(u16 *addr, u8 *data, int len)
int intel_scu_ipc_dev_writev(struct intel_scu_ipc_dev *scu, u16 *addr, u8 *data,
size_t len)
{
return pwr_reg_rdwr(addr, data, len, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
return pwr_reg_rdwr(scu, addr, data, len, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
}
EXPORT_SYMBOL(intel_scu_ipc_writev);
EXPORT_SYMBOL(intel_scu_ipc_dev_writev);
/**
* intel_scu_ipc_update_register - r/m/w a register
* intel_scu_ipc_dev_update() - Update a register
* @scu: Optional SCU IPC instance
* @addr: Register address
* @bits: Bits to update
* @data: Bits to update
* @mask: Mask of bits to update
*
* Read-modify-write power control unit register. The first data argument
@ -305,15 +411,17 @@ EXPORT_SYMBOL(intel_scu_ipc_writev);
* This function may sleep. Locking between SCU accesses is handled
* for the caller.
*/
int intel_scu_ipc_update_register(u16 addr, u8 bits, u8 mask)
int intel_scu_ipc_dev_update(struct intel_scu_ipc_dev *scu, u16 addr, u8 data,
u8 mask)
{
u8 data[2] = { bits, mask };
return pwr_reg_rdwr(&addr, data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_M);
u8 tmp[2] = { data, mask };
return pwr_reg_rdwr(scu, &addr, tmp, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_M);
}
EXPORT_SYMBOL(intel_scu_ipc_update_register);
EXPORT_SYMBOL(intel_scu_ipc_dev_update);
/**
* intel_scu_ipc_simple_command - send a simple command
* intel_scu_ipc_dev_simple_command() - Send a simple command
* @scu: Optional SCU IPC instance
* @cmd: Command
* @sub: Sub type
*
@ -324,62 +432,89 @@ EXPORT_SYMBOL(intel_scu_ipc_update_register);
* This function may sleep. Locking for SCU accesses is handled for the
* caller.
*/
int intel_scu_ipc_simple_command(int cmd, int sub)
int intel_scu_ipc_dev_simple_command(struct intel_scu_ipc_dev *scu, int cmd,
int sub)
{
struct intel_scu_ipc_dev *scu = &ipcdev;
u32 cmdval;
int err;
mutex_lock(&ipclock);
if (scu->dev == NULL) {
if (!scu)
scu = ipcdev;
if (!scu) {
mutex_unlock(&ipclock);
return -ENODEV;
}
ipc_command(scu, sub << 12 | cmd);
scu = ipcdev;
cmdval = sub << 12 | cmd;
ipc_command(scu, cmdval);
err = intel_scu_ipc_check_status(scu);
mutex_unlock(&ipclock);
if (err)
dev_err(&scu->dev, "IPC command %#x failed with %d\n", cmdval, err);
return err;
}
EXPORT_SYMBOL(intel_scu_ipc_simple_command);
EXPORT_SYMBOL(intel_scu_ipc_dev_simple_command);
/**
* intel_scu_ipc_command - command with data
* intel_scu_ipc_command_with_size() - Command with data
* @scu: Optional SCU IPC instance
* @cmd: Command
* @sub: Sub type
* @in: Input data
* @inlen: Input length in dwords
* @inlen: Input length in bytes
* @size: Input size written to the IPC command register in whatever
* units (dword, byte) the particular firmware requires. Normally
* should be the same as @inlen.
* @out: Output data
* @outlen: Output length in dwords
* @outlen: Output length in bytes
*
* Issue a command to the SCU which involves data transfers. Do the
* data copies under the lock but leave it for the caller to interpret.
*/
int intel_scu_ipc_command(int cmd, int sub, u32 *in, int inlen,
u32 *out, int outlen)
int intel_scu_ipc_dev_command_with_size(struct intel_scu_ipc_dev *scu, int cmd,
int sub, const void *in, size_t inlen,
size_t size, void *out, size_t outlen)
{
struct intel_scu_ipc_dev *scu = &ipcdev;
size_t outbuflen = DIV_ROUND_UP(outlen, sizeof(u32));
size_t inbuflen = DIV_ROUND_UP(inlen, sizeof(u32));
u32 cmdval, inbuf[4] = {};
int i, err;
if (inbuflen > 4 || outbuflen > 4)
return -EINVAL;
mutex_lock(&ipclock);
if (scu->dev == NULL) {
if (!scu)
scu = ipcdev;
if (!scu) {
mutex_unlock(&ipclock);
return -ENODEV;
}
for (i = 0; i < inlen; i++)
ipc_data_writel(scu, *in++, 4 * i);
memcpy(inbuf, in, inlen);
for (i = 0; i < inbuflen; i++)
ipc_data_writel(scu, inbuf[i], 4 * i);
ipc_command(scu, (inlen << 16) | (sub << 12) | cmd);
cmdval = (size << 16) | (sub << 12) | cmd;
ipc_command(scu, cmdval);
err = intel_scu_ipc_check_status(scu);
if (!err) {
for (i = 0; i < outlen; i++)
*out++ = ipc_data_readl(scu, 4 * i);
u32 outbuf[4] = {};
for (i = 0; i < outbuflen; i++)
outbuf[i] = ipc_data_readl(scu, 4 * i);
memcpy(out, outbuf, outlen);
}
mutex_unlock(&ipclock);
if (err)
dev_err(&scu->dev, "IPC command %#x failed with %d\n", cmdval, err);
return err;
}
EXPORT_SYMBOL(intel_scu_ipc_command);
EXPORT_SYMBOL(intel_scu_ipc_dev_command_with_size);
/*
* Interrupt handler gets called when ioc bit of IPC_COMMAND_REG set to 1
@ -399,61 +534,179 @@ static irqreturn_t ioc(int irq, void *dev_id)
return IRQ_HANDLED;
}
/**
* ipc_probe - probe an Intel SCU IPC
* @pdev: the PCI device matching
* @id: entry in the match table
*
* Enable and install an intel SCU IPC. This appears in the PCI space
* but uses some hard coded addresses as well.
*/
static int ipc_probe(struct pci_dev *pdev, const struct pci_device_id *id)
static void intel_scu_ipc_release(struct device *dev)
{
int err;
struct intel_scu_ipc_dev *scu = &ipcdev;
struct intel_scu_ipc_dev *scu;
if (scu->dev) /* We support only one SCU */
return -EBUSY;
err = pcim_enable_device(pdev);
if (err)
return err;
err = pcim_iomap_regions(pdev, 1 << 0, pci_name(pdev));
if (err)
return err;
init_completion(&scu->cmd_complete);
scu->ipc_base = pcim_iomap_table(pdev)[0];
err = devm_request_irq(&pdev->dev, pdev->irq, ioc, 0, "intel_scu_ipc",
scu);
if (err)
return err;
/* Assign device at last */
scu->dev = &pdev->dev;
intel_scu_devices_create();
pci_set_drvdata(pdev, scu);
return 0;
scu = container_of(dev, struct intel_scu_ipc_dev, dev);
if (scu->irq > 0)
free_irq(scu->irq, scu);
iounmap(scu->ipc_base);
release_mem_region(scu->mem.start, resource_size(&scu->mem));
kfree(scu);
}
static const struct pci_device_id pci_ids[] = {
{ PCI_VDEVICE(INTEL, 0x080e) },
{ PCI_VDEVICE(INTEL, 0x08ea) },
{ PCI_VDEVICE(INTEL, 0x11a0) },
{}
};
/**
* __intel_scu_ipc_register() - Register SCU IPC device
* @parent: Parent device
* @scu_data: Data used to configure SCU IPC
* @owner: Module registering the SCU IPC device
*
* Call this function to register SCU IPC mechanism under @parent.
* Returns pointer to the new SCU IPC device or ERR_PTR() in case of
* failure. The caller may use the returned instance if it needs to do
* SCU IPC calls itself.
*/
struct intel_scu_ipc_dev *
__intel_scu_ipc_register(struct device *parent,
const struct intel_scu_ipc_data *scu_data,
struct module *owner)
{
int err;
struct intel_scu_ipc_dev *scu;
void __iomem *ipc_base;
static struct pci_driver ipc_driver = {
.driver = {
.suppress_bind_attrs = true,
},
.name = "intel_scu_ipc",
.id_table = pci_ids,
.probe = ipc_probe,
};
builtin_pci_driver(ipc_driver);
mutex_lock(&ipclock);
/* We support only one IPC */
if (ipcdev) {
err = -EBUSY;
goto err_unlock;
}
scu = kzalloc(sizeof(*scu), GFP_KERNEL);
if (!scu) {
err = -ENOMEM;
goto err_unlock;
}
scu->owner = owner;
scu->dev.parent = parent;
scu->dev.class = &intel_scu_ipc_class;
scu->dev.release = intel_scu_ipc_release;
dev_set_name(&scu->dev, "intel_scu_ipc");
if (!request_mem_region(scu_data->mem.start, resource_size(&scu_data->mem),
"intel_scu_ipc")) {
err = -EBUSY;
goto err_free;
}
ipc_base = ioremap(scu_data->mem.start, resource_size(&scu_data->mem));
if (!ipc_base) {
err = -ENOMEM;
goto err_release;
}
scu->ipc_base = ipc_base;
scu->mem = scu_data->mem;
scu->irq = scu_data->irq;
init_completion(&scu->cmd_complete);
if (scu->irq > 0) {
err = request_irq(scu->irq, ioc, 0, "intel_scu_ipc", scu);
if (err)
goto err_unmap;
}
/*
* After this point intel_scu_ipc_release() takes care of
* releasing the SCU IPC resources once refcount drops to zero.
*/
err = device_register(&scu->dev);
if (err) {
put_device(&scu->dev);
goto err_unlock;
}
/* Assign device at last */
ipcdev = scu;
mutex_unlock(&ipclock);
return scu;
err_unmap:
iounmap(ipc_base);
err_release:
release_mem_region(scu_data->mem.start, resource_size(&scu_data->mem));
err_free:
kfree(scu);
err_unlock:
mutex_unlock(&ipclock);
return ERR_PTR(err);
}
EXPORT_SYMBOL_GPL(__intel_scu_ipc_register);
/**
* intel_scu_ipc_unregister() - Unregister SCU IPC
* @scu: SCU IPC handle
*
* This unregisters the SCU IPC device and releases the acquired
* resources once the refcount goes to zero.
*/
void intel_scu_ipc_unregister(struct intel_scu_ipc_dev *scu)
{
mutex_lock(&ipclock);
if (!WARN_ON(!ipcdev)) {
ipcdev = NULL;
device_unregister(&scu->dev);
}
mutex_unlock(&ipclock);
}
EXPORT_SYMBOL_GPL(intel_scu_ipc_unregister);
static void devm_intel_scu_ipc_unregister(struct device *dev, void *res)
{
struct intel_scu_ipc_devres *dr = res;
struct intel_scu_ipc_dev *scu = dr->scu;
intel_scu_ipc_unregister(scu);
}
/**
* __devm_intel_scu_ipc_register() - Register managed SCU IPC device
* @parent: Parent device
* @scu_data: Data used to configure SCU IPC
* @owner: Module registering the SCU IPC device
*
* Call this function to register managed SCU IPC mechanism under
* @parent. Returns pointer to the new SCU IPC device or ERR_PTR() in
* case of failure. The caller may use the returned instance if it needs
* to do SCU IPC calls itself.
*/
struct intel_scu_ipc_dev *
__devm_intel_scu_ipc_register(struct device *parent,
const struct intel_scu_ipc_data *scu_data,
struct module *owner)
{
struct intel_scu_ipc_devres *dr;
struct intel_scu_ipc_dev *scu;
dr = devres_alloc(devm_intel_scu_ipc_unregister, sizeof(*dr), GFP_KERNEL);
if (!dr)
return NULL;
scu = __intel_scu_ipc_register(parent, scu_data, owner);
if (IS_ERR(scu)) {
devres_free(dr);
return scu;
}
dr->scu = scu;
devres_add(parent, dr);
return scu;
}
EXPORT_SYMBOL_GPL(__devm_intel_scu_ipc_register);
static int __init intel_scu_ipc_init(void)
{
return class_register(&intel_scu_ipc_class);
}
subsys_initcall(intel_scu_ipc_init);
static void __exit intel_scu_ipc_exit(void)
{
class_unregister(&intel_scu_ipc_class);
}
module_exit(intel_scu_ipc_exit);

View File

@ -22,6 +22,9 @@
static int major;
struct intel_scu_ipc_dev *scu;
static DEFINE_MUTEX(scu_lock);
/* IOCTL commands */
#define INTE_SCU_IPC_REGISTER_READ 0
#define INTE_SCU_IPC_REGISTER_WRITE 1
@ -52,12 +55,12 @@ static int scu_reg_access(u32 cmd, struct scu_ipc_data *data)
switch (cmd) {
case INTE_SCU_IPC_REGISTER_READ:
return intel_scu_ipc_readv(data->addr, data->data, count);
return intel_scu_ipc_dev_readv(scu, data->addr, data->data, count);
case INTE_SCU_IPC_REGISTER_WRITE:
return intel_scu_ipc_writev(data->addr, data->data, count);
return intel_scu_ipc_dev_writev(scu, data->addr, data->data, count);
case INTE_SCU_IPC_REGISTER_UPDATE:
return intel_scu_ipc_update_register(data->addr[0],
data->data[0], data->mask);
return intel_scu_ipc_dev_update(scu, data->addr[0], data->data[0],
data->mask);
default:
return -ENOTTY;
}
@ -91,8 +94,40 @@ static long scu_ipc_ioctl(struct file *fp, unsigned int cmd,
return 0;
}
static int scu_ipc_open(struct inode *inode, struct file *file)
{
int ret = 0;
/* Only single open at the time */
mutex_lock(&scu_lock);
if (scu) {
ret = -EBUSY;
goto unlock;
}
scu = intel_scu_ipc_dev_get();
if (!scu)
ret = -ENODEV;
unlock:
mutex_unlock(&scu_lock);
return ret;
}
static int scu_ipc_release(struct inode *inode, struct file *file)
{
mutex_lock(&scu_lock);
intel_scu_ipc_dev_put(scu);
scu = NULL;
mutex_unlock(&scu_lock);
return 0;
}
static const struct file_operations scu_ipc_fops = {
.unlocked_ioctl = scu_ipc_ioctl,
.open = scu_ipc_open,
.release = scu_ipc_release,
};
static int __init ipc_module_init(void)

View File

@ -0,0 +1,68 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PCI driver for the Intel SCU.
*
* Copyright (C) 2008-2010, 2015, 2020 Intel Corporation
* Authors: Sreedhara DS (sreedhara.ds@intel.com)
* Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <asm/intel-mid.h>
#include <asm/intel_scu_ipc.h>
static int intel_scu_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
void (*setup_fn)(void) = (void (*)(void))id->driver_data;
struct intel_scu_ipc_data scu_data = {};
struct intel_scu_ipc_dev *scu;
int ret;
ret = pcim_enable_device(pdev);
if (ret)
return ret;
scu_data.mem = pdev->resource[0];
scu_data.irq = pdev->irq;
scu = intel_scu_ipc_register(&pdev->dev, &scu_data);
if (IS_ERR(scu))
return PTR_ERR(scu);
if (setup_fn)
setup_fn();
return 0;
}
static void intel_mid_scu_setup(void)
{
intel_scu_devices_create();
}
static const struct pci_device_id pci_ids[] = {
{ PCI_VDEVICE(INTEL, 0x080e),
.driver_data = (kernel_ulong_t)intel_mid_scu_setup },
{ PCI_VDEVICE(INTEL, 0x08ea),
.driver_data = (kernel_ulong_t)intel_mid_scu_setup },
{ PCI_VDEVICE(INTEL, 0x0a94) },
{ PCI_VDEVICE(INTEL, 0x11a0),
.driver_data = (kernel_ulong_t)intel_mid_scu_setup },
{ PCI_VDEVICE(INTEL, 0x1a94) },
{ PCI_VDEVICE(INTEL, 0x5a94) },
{}
};
static struct pci_driver intel_scu_pci_driver = {
.driver = {
.suppress_bind_attrs = true,
},
.name = "intel_scu",
.id_table = pci_ids,
.probe = intel_scu_pci_probe,
};
builtin_pci_driver(intel_scu_pci_driver);

View File

@ -353,21 +353,16 @@ int telemetry_clear_pltdata(void)
EXPORT_SYMBOL_GPL(telemetry_clear_pltdata);
/**
* telemetry_pltconfig_valid() - Checkif platform config is valid
* telemetry_get_pltdata() - Return telemetry platform config
*
* Usage by other than telemetry module is invalid
*
* Return: 0 success, < 0 for failure
* May be used by other telemetry modules to get platform specific
* configuration.
*/
int telemetry_pltconfig_valid(void)
struct telemetry_plt_config *telemetry_get_pltdata(void)
{
if (telm_core_conf.plt_config)
return 0;
else
return -EINVAL;
return telm_core_conf.plt_config;
}
EXPORT_SYMBOL_GPL(telemetry_pltconfig_valid);
EXPORT_SYMBOL_GPL(telemetry_get_pltdata);
static inline int telemetry_get_pssevtname(enum telemetry_unit telem_unit,
const char **name, int len)

View File

@ -15,6 +15,7 @@
*/
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/mfd/intel_pmc_bxt.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/seq_file.h>
@ -22,7 +23,6 @@
#include <asm/cpu_device_id.h>
#include <asm/intel-family.h>
#include <asm/intel_pmc_ipc.h>
#include <asm/intel_telemetry.h>
#define DRIVER_NAME "telemetry_soc_debugfs"
@ -647,10 +647,11 @@ DEFINE_SHOW_ATTRIBUTE(telem_soc_states);
static int telem_s0ix_res_get(void *data, u64 *val)
{
struct telemetry_plt_config *plt_config = telemetry_get_pltdata();
u64 s0ix_total_res;
int ret;
ret = intel_pmc_s0ix_counter_read(&s0ix_total_res);
ret = intel_pmc_s0ix_counter_read(plt_config->pmc, &s0ix_total_res);
if (ret) {
pr_err("Failed to read S0ix residency");
return ret;
@ -837,12 +838,15 @@ static int pm_suspend_exit_cb(void)
*/
if (suspend_shlw_ctr_exit == suspend_shlw_ctr_temp &&
suspend_deep_ctr_exit == suspend_deep_ctr_temp) {
ret = intel_pmc_gcr_read64(PMC_GCR_TELEM_SHLW_S0IX_REG,
struct telemetry_plt_config *plt_config = telemetry_get_pltdata();
struct intel_pmc_dev *pmc = plt_config->pmc;
ret = intel_pmc_gcr_read64(pmc, PMC_GCR_TELEM_SHLW_S0IX_REG,
&suspend_shlw_res_exit);
if (ret < 0)
goto out;
ret = intel_pmc_gcr_read64(PMC_GCR_TELEM_DEEP_S0IX_REG,
ret = intel_pmc_gcr_read64(pmc, PMC_GCR_TELEM_DEEP_S0IX_REG,
&suspend_deep_res_exit);
if (ret < 0)
goto out;
@ -910,8 +914,7 @@ static int __init telemetry_debugfs_init(void)
debugfs_conf = (struct telemetry_debugfs_conf *)id->driver_data;
err = telemetry_pltconfig_valid();
if (err < 0) {
if (!telemetry_get_pltdata()) {
pr_info("Invalid pltconfig, ensure IPC1 device is enabled in BIOS\n");
return -ENODEV;
}

View File

@ -15,7 +15,6 @@
#include <asm/cpu_device_id.h>
#include <asm/intel-family.h>
#include <asm/intel_pmc_ipc.h>
#include <asm/intel_punit_ipc.h>
#include <asm/intel_telemetry.h>
@ -35,6 +34,7 @@
#define TELEM_SSRAM_STARTTIME_OFFSET 8
#define TELEM_SSRAM_EVTLOG_OFFSET 16
#define IOSS_TELEM 0xeb
#define IOSS_TELEM_EVENT_READ 0x0
#define IOSS_TELEM_EVENT_WRITE 0x1
#define IOSS_TELEM_INFO_READ 0x2
@ -42,9 +42,6 @@
#define IOSS_TELEM_TRACE_CTL_WRITE 0x6
#define IOSS_TELEM_EVENT_CTL_READ 0x7
#define IOSS_TELEM_EVENT_CTL_WRITE 0x8
#define IOSS_TELEM_EVT_CTRL_WRITE_SIZE 0x4
#define IOSS_TELEM_READ_WORD 0x1
#define IOSS_TELEM_WRITE_FOURBYTES 0x4
#define IOSS_TELEM_EVT_WRITE_SIZE 0x3
#define TELEM_INFO_SRAMEVTS_MASK 0xFF00
@ -250,17 +247,14 @@ static int telemetry_check_evtid(enum telemetry_unit telem_unit,
static inline int telemetry_plt_config_ioss_event(u32 evt_id, int index)
{
u32 write_buf;
int ret;
write_buf = evt_id | TELEM_EVENT_ENABLE;
write_buf <<= BITS_PER_BYTE;
write_buf |= index;
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
IOSS_TELEM_EVENT_WRITE, (u8 *)&write_buf,
IOSS_TELEM_EVT_WRITE_SIZE, NULL, 0);
return ret;
return intel_scu_ipc_dev_command(telm_conf->scu, IOSS_TELEM,
IOSS_TELEM_EVENT_WRITE, &write_buf,
IOSS_TELEM_EVT_WRITE_SIZE, NULL, 0);
}
static inline int telemetry_plt_config_pss_event(u32 evt_id, int index)
@ -278,6 +272,7 @@ static inline int telemetry_plt_config_pss_event(u32 evt_id, int index)
static int telemetry_setup_iossevtconfig(struct telemetry_evtconfig evtconfig,
enum telemetry_action action)
{
struct intel_scu_ipc_dev *scu = telm_conf->scu;
u8 num_ioss_evts, ioss_period;
int ret, index, idx;
u32 *ioss_evtmap;
@ -288,9 +283,9 @@ static int telemetry_setup_iossevtconfig(struct telemetry_evtconfig evtconfig,
ioss_evtmap = evtconfig.evtmap;
/* Get telemetry EVENT CTL */
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
IOSS_TELEM_EVENT_CTL_READ, NULL, 0,
&telem_ctrl, IOSS_TELEM_READ_WORD);
&telem_ctrl, sizeof(telem_ctrl));
if (ret) {
pr_err("IOSS TELEM_CTRL Read Failed\n");
return ret;
@ -299,11 +294,9 @@ static int telemetry_setup_iossevtconfig(struct telemetry_evtconfig evtconfig,
/* Disable Telemetry */
TELEM_DISABLE(telem_ctrl);
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
IOSS_TELEM_EVENT_CTL_WRITE,
(u8 *)&telem_ctrl,
IOSS_TELEM_EVT_CTRL_WRITE_SIZE,
NULL, 0);
ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
IOSS_TELEM_EVENT_CTL_WRITE, &telem_ctrl,
sizeof(telem_ctrl), NULL, 0);
if (ret) {
pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n");
return ret;
@ -315,10 +308,9 @@ static int telemetry_setup_iossevtconfig(struct telemetry_evtconfig evtconfig,
/* Clear All Events */
TELEM_CLEAR_EVENTS(telem_ctrl);
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
IOSS_TELEM_EVENT_CTL_WRITE,
(u8 *)&telem_ctrl,
IOSS_TELEM_EVT_CTRL_WRITE_SIZE,
&telem_ctrl, sizeof(telem_ctrl),
NULL, 0);
if (ret) {
pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n");
@ -344,10 +336,9 @@ static int telemetry_setup_iossevtconfig(struct telemetry_evtconfig evtconfig,
/* Clear All Events */
TELEM_CLEAR_EVENTS(telem_ctrl);
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
IOSS_TELEM_EVENT_CTL_WRITE,
(u8 *)&telem_ctrl,
IOSS_TELEM_EVT_CTRL_WRITE_SIZE,
&telem_ctrl, sizeof(telem_ctrl),
NULL, 0);
if (ret) {
pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n");
@ -396,10 +387,9 @@ static int telemetry_setup_iossevtconfig(struct telemetry_evtconfig evtconfig,
TELEM_ENABLE_PERIODIC(telem_ctrl);
telem_ctrl |= ioss_period;
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
IOSS_TELEM_EVENT_CTL_WRITE,
(u8 *)&telem_ctrl,
IOSS_TELEM_EVT_CTRL_WRITE_SIZE, NULL, 0);
&telem_ctrl, sizeof(telem_ctrl), NULL, 0);
if (ret) {
pr_err("IOSS TELEM_CTRL Event Enable Write Failed\n");
return ret;
@ -586,8 +576,9 @@ static int telemetry_setup(struct platform_device *pdev)
u32 read_buf, events, event_regs;
int ret;
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY, IOSS_TELEM_INFO_READ,
NULL, 0, &read_buf, IOSS_TELEM_READ_WORD);
ret = intel_scu_ipc_dev_command(telm_conf->scu, IOSS_TELEM,
IOSS_TELEM_INFO_READ, NULL, 0,
&read_buf, sizeof(read_buf));
if (ret) {
dev_err(&pdev->dev, "IOSS TELEM_INFO Read Failed\n");
return ret;
@ -681,6 +672,8 @@ static int telemetry_plt_set_sampling_period(u8 pss_period, u8 ioss_period)
mutex_lock(&(telm_conf->telem_lock));
if (ioss_period) {
struct intel_scu_ipc_dev *scu = telm_conf->scu;
if (TELEM_SAMPLE_PERIOD_INVALID(ioss_period)) {
pr_err("IOSS Sampling Period Out of Range\n");
ret = -EINVAL;
@ -688,9 +681,9 @@ static int telemetry_plt_set_sampling_period(u8 pss_period, u8 ioss_period)
}
/* Get telemetry EVENT CTL */
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
IOSS_TELEM_EVENT_CTL_READ, NULL, 0,
&telem_ctrl, IOSS_TELEM_READ_WORD);
&telem_ctrl, sizeof(telem_ctrl));
if (ret) {
pr_err("IOSS TELEM_CTRL Read Failed\n");
goto out;
@ -699,11 +692,10 @@ static int telemetry_plt_set_sampling_period(u8 pss_period, u8 ioss_period)
/* Disable Telemetry */
TELEM_DISABLE(telem_ctrl);
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
IOSS_TELEM_EVENT_CTL_WRITE,
(u8 *)&telem_ctrl,
IOSS_TELEM_EVT_CTRL_WRITE_SIZE,
NULL, 0);
ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
IOSS_TELEM_EVENT_CTL_WRITE,
&telem_ctrl, sizeof(telem_ctrl),
NULL, 0);
if (ret) {
pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n");
goto out;
@ -715,11 +707,10 @@ static int telemetry_plt_set_sampling_period(u8 pss_period, u8 ioss_period)
TELEM_ENABLE_PERIODIC(telem_ctrl);
telem_ctrl |= ioss_period;
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
IOSS_TELEM_EVENT_CTL_WRITE,
(u8 *)&telem_ctrl,
IOSS_TELEM_EVT_CTRL_WRITE_SIZE,
NULL, 0);
ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
IOSS_TELEM_EVENT_CTL_WRITE,
&telem_ctrl, sizeof(telem_ctrl),
NULL, 0);
if (ret) {
pr_err("IOSS TELEM_CTRL Event Enable Write Failed\n");
goto out;
@ -1014,9 +1005,9 @@ static int telemetry_plt_get_trace_verbosity(enum telemetry_unit telem_unit,
break;
case TELEM_IOSS:
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
IOSS_TELEM_TRACE_CTL_READ, NULL, 0, &temp,
IOSS_TELEM_READ_WORD);
ret = intel_scu_ipc_dev_command(telm_conf->scu,
IOSS_TELEM, IOSS_TELEM_TRACE_CTL_READ,
NULL, 0, &temp, sizeof(temp));
if (ret) {
pr_err("IOSS TRACE_CTL Read Failed\n");
goto out;
@ -1068,9 +1059,9 @@ static int telemetry_plt_set_trace_verbosity(enum telemetry_unit telem_unit,
break;
case TELEM_IOSS:
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
IOSS_TELEM_TRACE_CTL_READ, NULL, 0, &temp,
IOSS_TELEM_READ_WORD);
ret = intel_scu_ipc_dev_command(telm_conf->scu, IOSS_TELEM,
IOSS_TELEM_TRACE_CTL_READ,
NULL, 0, &temp, sizeof(temp));
if (ret) {
pr_err("IOSS TRACE_CTL Read Failed\n");
goto out;
@ -1079,9 +1070,9 @@ static int telemetry_plt_set_trace_verbosity(enum telemetry_unit telem_unit,
TELEM_CLEAR_VERBOSITY_BITS(temp);
TELEM_SET_VERBOSITY_BITS(temp, verbosity);
ret = intel_pmc_ipc_command(PMC_IPC_PMC_TELEMTRY,
IOSS_TELEM_TRACE_CTL_WRITE, (u8 *)&temp,
IOSS_TELEM_WRITE_FOURBYTES, NULL, 0);
ret = intel_scu_ipc_dev_command(telm_conf->scu, IOSS_TELEM,
IOSS_TELEM_TRACE_CTL_WRITE,
&temp, sizeof(temp), NULL, 0);
if (ret) {
pr_err("IOSS TRACE_CTL Verbosity Set Failed\n");
goto out;
@ -1124,6 +1115,8 @@ static int telemetry_pltdrv_probe(struct platform_device *pdev)
telm_conf = (struct telemetry_plt_config *)id->driver_data;
telm_conf->pmc = dev_get_drvdata(pdev->dev.parent);
mem = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(mem))
return PTR_ERR(mem);
@ -1136,6 +1129,12 @@ static int telemetry_pltdrv_probe(struct platform_device *pdev)
telm_conf->ioss_config.regmap = mem;
telm_conf->scu = devm_intel_scu_ipc_dev_get(&pdev->dev);
if (!telm_conf->scu) {
ret = -EPROBE_DEFER;
goto out;
}
mutex_init(&telm_conf->telem_lock);
mutex_init(&telm_conf->telem_trace_lock);

View File

@ -11,7 +11,7 @@ config TYPEC_MUX_PI3USB30532
config TYPEC_MUX_INTEL_PMC
tristate "Intel PMC mux control"
depends on INTEL_PMC_IPC
depends on INTEL_SCU_IPC
select USB_ROLE_SWITCH
help
Driver for USB muxes controlled by Intel PMC FW. Intel PMC FW can

View File

@ -15,7 +15,7 @@
#include <linux/usb/typec_dp.h>
#include <linux/usb/typec_tbt.h>
#include <asm/intel_pmc_ipc.h>
#include <asm/intel_scu_ipc.h>
#define PMC_USBC_CMD 0xa7
@ -96,6 +96,7 @@ struct pmc_usb_port {
struct pmc_usb {
u8 num_ports;
struct device *dev;
struct intel_scu_ipc_dev *ipc;
struct pmc_usb_port *port;
};
@ -107,9 +108,8 @@ static int pmc_usb_command(struct pmc_usb_port *port, u8 *msg, u32 len)
* Error bit will always be 0 with the USBC command.
* Status can be checked from the response message.
*/
intel_pmc_ipc_command(PMC_USBC_CMD, 0, msg, len,
(void *)response, 1);
intel_scu_ipc_dev_command(port->pmc->ipc, PMC_USBC_CMD, 0, msg, len,
response, sizeof(response));
if (response[2]) {
if (response[2] & BIT(1))
return -EIO;
@ -370,6 +370,10 @@ static int pmc_usb_probe(struct platform_device *pdev)
if (!pmc->port)
return -ENOMEM;
pmc->ipc = devm_intel_scu_ipc_dev_get(&pdev->dev);
if (!pmc->ipc)
return -ENODEV;
pmc->dev = &pdev->dev;
/*

View File

@ -41,8 +41,8 @@ config TYPEC_FUSB302
config TYPEC_WCOVE
tristate "Intel WhiskeyCove PMIC USB Type-C PHY driver"
depends on ACPI
depends on MFD_INTEL_PMC_BXT
depends on INTEL_SOC_PMIC
depends on INTEL_PMC_IPC
depends on BXT_WC_PMIC_OPREGION
help
This driver adds support for USB Type-C on Intel Broxton platforms

View File

@ -64,6 +64,7 @@
#include <linux/uaccess.h> /* For copy_to_user/put_user/... */
#include <linux/io.h> /* For inb/outb/... */
#include <linux/platform_data/itco_wdt.h>
#include <linux/mfd/intel_pmc_bxt.h>
#include "iTCO_vendor.h"
@ -233,12 +234,24 @@ static int update_no_reboot_bit_cnt(void *priv, bool set)
return val != newval ? -EIO : 0;
}
static void iTCO_wdt_no_reboot_bit_setup(struct iTCO_wdt_private *p,
struct itco_wdt_platform_data *pdata)
static int update_no_reboot_bit_pmc(void *priv, bool set)
{
if (pdata->update_no_reboot_bit) {
p->update_no_reboot_bit = pdata->update_no_reboot_bit;
p->no_reboot_priv = pdata->no_reboot_priv;
struct intel_pmc_dev *pmc = priv;
u32 bits = PMC_CFG_NO_REBOOT_EN;
u32 value = set ? bits : 0;
return intel_pmc_gcr_update(pmc, PMC_GCR_PMC_CFG_REG, bits, value);
}
static void iTCO_wdt_no_reboot_bit_setup(struct iTCO_wdt_private *p,
struct platform_device *pdev,
struct itco_wdt_platform_data *pdata)
{
if (pdata->no_reboot_use_pmc) {
struct intel_pmc_dev *pmc = dev_get_drvdata(pdev->dev.parent);
p->update_no_reboot_bit = update_no_reboot_bit_pmc;
p->no_reboot_priv = pmc;
return;
}
@ -478,14 +491,14 @@ static int iTCO_wdt_probe(struct platform_device *pdev)
return -ENODEV;
}
iTCO_wdt_no_reboot_bit_setup(p, pdata);
iTCO_wdt_no_reboot_bit_setup(p, pdev, pdata);
/*
* Get the Memory-Mapped GCS or PMC register, we need it for the
* NO_REBOOT flag (TCO v2 and v3).
*/
if (p->iTCO_version >= 2 && p->iTCO_version < 6 &&
!pdata->update_no_reboot_bit) {
!pdata->no_reboot_use_pmc) {
p->gcs_pmc_res = platform_get_resource(pdev,
IORESOURCE_MEM,
ICH_RES_MEM_GCS_PMC);

View File

@ -33,14 +33,24 @@ enum {
SCU_WATCHDOG_KEEPALIVE,
};
static inline int wdt_command(int sub, u32 *in, int inlen)
struct mid_wdt {
struct watchdog_device wd;
struct device *dev;
struct intel_scu_ipc_dev *scu;
};
static inline int
wdt_command(struct mid_wdt *mid, int sub, const void *in, size_t inlen, size_t size)
{
return intel_scu_ipc_command(IPC_WATCHDOG, sub, in, inlen, NULL, 0);
struct intel_scu_ipc_dev *scu = mid->scu;
return intel_scu_ipc_dev_command_with_size(scu, IPC_WATCHDOG, sub, in,
inlen, size, NULL, 0);
}
static int wdt_start(struct watchdog_device *wd)
{
struct device *dev = watchdog_get_drvdata(wd);
struct mid_wdt *mid = watchdog_get_drvdata(wd);
int ret, in_size;
int timeout = wd->timeout;
struct ipc_wd_start {
@ -49,38 +59,41 @@ static int wdt_start(struct watchdog_device *wd)
} ipc_wd_start = { timeout - MID_WDT_PRETIMEOUT, timeout };
/*
* SCU expects the input size for watchdog IPC to
* be based on 4 bytes
* SCU expects the input size for watchdog IPC to be 2 which is the
* size of the structure in dwords. SCU IPC normally takes bytes
* but this is a special case where we specify size to be different
* than inlen.
*/
in_size = DIV_ROUND_UP(sizeof(ipc_wd_start), 4);
ret = wdt_command(SCU_WATCHDOG_START, (u32 *)&ipc_wd_start, in_size);
ret = wdt_command(mid, SCU_WATCHDOG_START, &ipc_wd_start,
sizeof(ipc_wd_start), in_size);
if (ret)
dev_crit(dev, "error starting watchdog: %d\n", ret);
dev_crit(mid->dev, "error starting watchdog: %d\n", ret);
return ret;
}
static int wdt_ping(struct watchdog_device *wd)
{
struct device *dev = watchdog_get_drvdata(wd);
struct mid_wdt *mid = watchdog_get_drvdata(wd);
int ret;
ret = wdt_command(SCU_WATCHDOG_KEEPALIVE, NULL, 0);
ret = wdt_command(mid, SCU_WATCHDOG_KEEPALIVE, NULL, 0, 0);
if (ret)
dev_crit(dev, "Error executing keepalive: %d\n", ret);
dev_crit(mid->dev, "Error executing keepalive: %d\n", ret);
return ret;
}
static int wdt_stop(struct watchdog_device *wd)
{
struct device *dev = watchdog_get_drvdata(wd);
struct mid_wdt *mid = watchdog_get_drvdata(wd);
int ret;
ret = wdt_command(SCU_WATCHDOG_STOP, NULL, 0);
ret = wdt_command(mid, SCU_WATCHDOG_STOP, NULL, 0, 0);
if (ret)
dev_crit(dev, "Error stopping watchdog: %d\n", ret);
dev_crit(mid->dev, "Error stopping watchdog: %d\n", ret);
return ret;
}
@ -110,6 +123,7 @@ static int mid_wdt_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct watchdog_device *wdt_dev;
struct intel_mid_wdt_pdata *pdata = dev->platform_data;
struct mid_wdt *mid;
int ret;
if (!pdata) {
@ -123,10 +137,13 @@ static int mid_wdt_probe(struct platform_device *pdev)
return ret;
}
wdt_dev = devm_kzalloc(dev, sizeof(*wdt_dev), GFP_KERNEL);
if (!wdt_dev)
mid = devm_kzalloc(dev, sizeof(*mid), GFP_KERNEL);
if (!mid)
return -ENOMEM;
mid->dev = dev;
wdt_dev = &mid->wd;
wdt_dev->info = &mid_wdt_info;
wdt_dev->ops = &mid_wdt_ops;
wdt_dev->min_timeout = MID_WDT_TIMEOUT_MIN;
@ -135,7 +152,7 @@ static int mid_wdt_probe(struct platform_device *pdev)
wdt_dev->parent = dev;
watchdog_set_nowayout(wdt_dev, WATCHDOG_NOWAYOUT);
watchdog_set_drvdata(wdt_dev, dev);
watchdog_set_drvdata(wdt_dev, mid);
ret = devm_request_irq(dev, pdata->irq, mid_wdt_irq,
IRQF_SHARED | IRQF_NO_SUSPEND, "watchdog",
@ -145,6 +162,10 @@ static int mid_wdt_probe(struct platform_device *pdev)
return ret;
}
mid->scu = devm_intel_scu_ipc_dev_get(dev);
if (!mid->scu)
return -EPROBE_DEFER;
/*
* The firmware followed by U-Boot leaves the watchdog running
* with the default threshold which may vary. When we get here

View File

@ -0,0 +1,53 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef MFD_INTEL_PMC_BXT_H
#define MFD_INTEL_PMC_BXT_H
/* GCR reg offsets from GCR base */
#define PMC_GCR_PMC_CFG_REG 0x08
#define PMC_GCR_TELEM_DEEP_S0IX_REG 0x78
#define PMC_GCR_TELEM_SHLW_S0IX_REG 0x80
/* PMC_CFG_REG bit masks */
#define PMC_CFG_NO_REBOOT_EN BIT(4)
/**
* struct intel_pmc_dev - Intel PMC device structure
* @dev: Pointer to the parent PMC device
* @scu: Pointer to the SCU IPC device data structure
* @gcr_mem_base: Virtual base address of GCR (Global Configuration Registers)
* @gcr_lock: Lock used to serialize access to GCR registers
* @telem_base: Pointer to telemetry SSRAM base resource or %NULL if not
* available
*/
struct intel_pmc_dev {
struct device *dev;
struct intel_scu_ipc_dev *scu;
void __iomem *gcr_mem_base;
spinlock_t gcr_lock;
struct resource *telem_base;
};
#if IS_ENABLED(CONFIG_MFD_INTEL_PMC_BXT)
int intel_pmc_gcr_read64(struct intel_pmc_dev *pmc, u32 offset, u64 *data);
int intel_pmc_gcr_update(struct intel_pmc_dev *pmc, u32 offset, u32 mask, u32 val);
int intel_pmc_s0ix_counter_read(struct intel_pmc_dev *pmc, u64 *data);
#else
static inline int intel_pmc_gcr_read64(struct intel_pmc_dev *pmc, u32 offset,
u64 *data)
{
return -ENOTSUPP;
}
static inline int intel_pmc_gcr_update(struct intel_pmc_dev *pmc, u32 offset,
u32 mask, u32 val)
{
return -ENOTSUPP;
}
static inline int intel_pmc_s0ix_counter_read(struct intel_pmc_dev *pmc, u64 *data)
{
return -ENOTSUPP;
}
#endif
#endif /* MFD_INTEL_PMC_BXT_H */

View File

@ -13,6 +13,20 @@
#include <linux/regmap.h>
/**
* struct intel_soc_pmic - Intel SoC PMIC data
* @irq: Master interrupt number of the parent PMIC device
* @regmap: Pointer to the parent PMIC device regmap structure
* @irq_chip_data: IRQ chip data for the PMIC itself
* @irq_chip_data_pwrbtn: Chained IRQ chip data for the Power Button
* @irq_chip_data_tmu: Chained IRQ chip data for the Time Management Unit
* @irq_chip_data_bcu: Chained IRQ chip data for the Burst Control Unit
* @irq_chip_data_adc: Chained IRQ chip data for the General Purpose ADC
* @irq_chip_data_chgr: Chained IRQ chip data for the External Charger
* @irq_chip_data_crit: Chained IRQ chip data for the Critical Event Handler
* @dev: Pointer to the parent PMIC device
* @scu: Pointer to the SCU IPC device data structure
*/
struct intel_soc_pmic {
int irq;
struct regmap *regmap;
@ -24,6 +38,7 @@ struct intel_soc_pmic {
struct regmap_irq_chip_data *irq_chip_data_chgr;
struct regmap_irq_chip_data *irq_chip_data_crit;
struct device *dev;
struct intel_scu_ipc_dev *scu;
};
int intel_soc_pmic_exec_mipi_pmic_seq_element(u16 i2c_address, u32 reg_address,

View File

@ -12,13 +12,16 @@
#define ICH_RES_MEM_OFF 2
#define ICH_RES_MEM_GCS_PMC 0
/**
* struct itco_wdt_platform_data - iTCO_wdt platform data
* @name: Name of the platform
* @version: iTCO version
* @no_reboot_use_pmc: Use PMC BXT API to set and clear NO_REBOOT bit
*/
struct itco_wdt_platform_data {
char name[32];
unsigned int version;
/* private data to be passed to update_no_reboot_bit API */
void *no_reboot_priv;
/* pointer for platform specific no reboot update function */
int (*update_no_reboot_bit)(void *priv, bool set);
bool no_reboot_use_pmc;
};
#endif /* _ITCO_WDT_H_ */