// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for Pondicherry2 memory controller.
*
* Copyright (c) 2016, Intel Corporation.
*
* [Derived from sb_edac.c]
*
* Translation of system physical addresses to DIMM addresses
* is a two stage process:
*
* First the Pondicherry 2 memory controller handles slice and channel interleaving
* in "sys2pmi()". This is (almost) completley common between platforms.
*
* Then a platform specific dunit (DIMM unit) completes the process to provide DIMM,
* rank, bank, row and column using the appropriate "dunit_ops" functions/parameters.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/pci_ids.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/edac.h>
#include <linux/mmzone.h>
#include <linux/smp.h>
#include <linux/bitmap.h>
#include <linux/math64.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_data/x86/p2sb.h>
#include <asm/cpu_device_id.h>
#include <asm/intel-family.h>
#include <asm/processor.h>
#include <asm/mce.h>
#include "edac_mc.h"
#include "edac_module.h"
#include "pnd2_edac.h"
#define EDAC_MOD_STR "pnd2_edac"
#define APL_NUM_CHANNELS 4
#define DNV_NUM_CHANNELS 2
#define DNV_MAX_DIMMS 2 /* Max DIMMs per channel */
enum type {
APL,
DNV, /* All requests go to PMI CH0 on each slice (CH1 disabled) */
};
struct dram_addr {
int chan;
int dimm;
int rank;
int bank;
int row;
int col;
};
struct pnd2_pvt {
int dimm_geom[APL_NUM_CHANNELS];
u64 tolm, tohm;
};
/*
* System address space is divided into multiple regions with
* different interleave rules in each. The as0/as1 regions
* have no interleaving at all. The as2 region is interleaved
* between two channels. The mot region is magic and may overlap
* other regions, with its interleave rules taking precedence.
* Addresses not in any of these regions are interleaved across
* all four channels.
*/
static struct region {
u64 base;
u64 limit;
u8 enabled;
} mot, as0, as1, as2;
static struct dunit_ops {
char *name;
enum type type;
int pmiaddr_shift;
int pmiidx_shift;
int channels;
int dimms_per_channel;
int (*rd_reg)(int port, int off, int op, void *data, size_t sz, char *name);
int (*get_registers)(void);
int (*check_ecc)(void);
void (*mk_region)(char *name, struct region *rp, void *asym);
void (*get_dimm_config)(struct mem_ctl_info *mci);
int (*pmi2mem)(struct mem_ctl_info *mci, u64 pmiaddr, u32 pmiidx,
struct dram_addr *daddr, char *msg);
} *ops;
static struct mem_ctl_info *pnd2_mci;
#define PND2_MSG_SIZE 256
/* Debug macros */
#define pnd2_printk(level, fmt, arg...) \
edac_printk(level, "pnd2", fmt, ##arg)
#define pnd2_mc_printk(mci, level, fmt, arg...) \
edac_mc_chipset_printk(mci, level, "pnd2", fmt, ##arg)
#define MOT_CHAN_INTLV_BIT_1SLC_2CH 12
#define MOT_CHAN_INTLV_BIT_2SLC_2CH 13
#define SELECTOR_DISABLED (-1)
#define _4GB (1ul << 32)
#define PMI_ADDRESS_WIDTH 31
#define PND_MAX_PHYS_BIT 39
#define APL_ASYMSHIFT 28
#define DNV_ASYMSHIFT 31
#define CH_HASH_MASK_LSB 6
#define SLICE_HASH_MASK_LSB 6
#define MOT_SLC_INTLV_BIT 12
#define LOG2_PMI_ADDR_GRANULARITY 5
#define MOT_SHIFT 24
#define GET_BITFIELD(v, lo, hi) (((v) & GENMASK_ULL(hi, lo)) >> (lo))
#define U64_LSHIFT(val, s) ((u64)(val) << (s))
/*
* On Apollo Lake we access memory controller registers via a
* side-band mailbox style interface in a hidden PCI device
* configuration space.
*/
static struct pci_bus *p2sb_bus;
#define P2SB_DEVFN PCI_DEVFN(0xd, 0)
#define P2SB_ADDR_OFF 0xd0
#define P2SB_DATA_OFF 0xd4
#define P2SB_STAT_OFF 0xd8
#define P2SB_ROUT_OFF 0xda
#define P2SB_EADD_OFF 0xdc
#define P2SB_HIDE_OFF 0xe1
#define P2SB_BUSY 1
#define P2SB_READ(size, off, ptr) \
pci_bus_read_config_##size(p2sb_bus, P2SB_DEVFN, off, ptr)
#define P2SB_WRITE(size, off, val) \
pci_bus_write_config_##size(p2sb_bus, P2SB_DEVFN, off, val)
static bool p2sb_is_busy(u16 *status)
{
P2SB_READ(word, P2SB_STAT_OFF, status);
return !!(*status & P2SB_BUSY);
}
static int _apl_rd_reg(int port, int off, int op, u32 *data)
{
int retries = 0xff, ret;
u16 status;
u8 hidden;
/* Unhide the P2SB device, if it's hidden */
P2SB_READ(byte, P2SB_HIDE_OFF, &hidden);
if (hidden)
P2SB_WRITE(byte, P2SB_HIDE_OFF, 0);
if (p2sb_is_busy(&status)) {
ret = -EAGAIN;
goto out;
}
P2SB_WRITE(dword, P2SB_ADDR_OFF, (port << 24) | off);
P2SB_WRITE(dword, P2SB_DATA_OFF, 0);
P2SB_WRITE(dword, P2SB_EADD_OFF, 0);
P2SB_WRITE(word, P2SB_ROUT_OFF, 0);
P2SB_WRITE(word, P2SB_STAT_OFF, (op << 8) | P2SB_BUSY);
while (p2sb_is_busy(&status)) {
if (retries-- == 0) {
ret = -EBUSY;
goto out;
}
}
P2SB_READ(dword, P2SB_DATA_OFF, data);
ret = (status >> 1) & 0x3;
out:
/* Hide the P2SB device, if it was hidden before */
if (hidden)
P2SB_WRITE(byte, P2SB_HIDE_OFF, hidden);
return ret;
}
static int apl_rd_reg(int port, int off, int op, void *data, size_t sz, char *name)
{
int ret = 0;
edac_dbg(2, "Read %s port=%x off=%x op=%x\n", name, port, off, op);
switch (sz) {
case 8:
ret = _apl_rd_reg(port, off + 4, op, (u32 *)(data + 4));
fallthrough;
case 4:
ret |= _apl_rd_reg(port, off, op, (u32 *)data);
pnd2_printk(KERN_DEBUG, "%s=%x%08x ret=%d\n", name,
sz == 8 ? *((u32 *)(data + 4)) : 0, *((u32 *)data), ret);
break;
}
return ret;
}
static u64 get_mem_ctrl_hub_base_addr(void)
{
struct b_cr_mchbar_lo_pci lo;
struct b_cr_mchbar_hi_pci hi;
struct pci_dev *pdev;
pdev = pci_get_device(PCI_VENDOR_ID_INTEL, 0x1980, NULL);
if (pdev) {
pci_read_config_dword(pdev, 0x48, (u32 *)&lo);
pci_read_config_dword(pdev, 0x4c, (u32 *)&hi);
pci_dev_put(pdev);
} else {
return 0;
}
if (!lo.enable) {
edac_dbg(2, "MMIO via memory controller hub base address is disabled!\n");
return
|