604 lines
14 KiB
C
604 lines
14 KiB
C
/*
|
|
* stm.c - MIPI STM Debug Unit driver
|
|
*
|
|
* Copyright (C) Intel 2013
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
*
|
|
* The STM (Sytem Trace Macro) Unit driver configure trace output
|
|
* to the Intel Tangier PTI port and DWC3 USB xHCI controller
|
|
* out of the mobile device for analysis with a debugging tool
|
|
* (Lauterbach, Fido). This is part of a solution for the MIPI P1149.7,
|
|
* compact JTAG, standard and USB Debug-Class
|
|
*
|
|
* This header file will allow other parts of the OS to use the
|
|
* interface to write out it's contents for debugging a mobile system.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/sdm.h>
|
|
|
|
#include "stm.h"
|
|
#include <asm/intel-mid.h>
|
|
#include <asm/intel_soc_debug.h>
|
|
#include "../usb/dwc3/core.h"
|
|
|
|
/* STM Registers */
|
|
#define STM_CTRL 0x0000
|
|
#define STM_USB3DBGGTHR 0x0008
|
|
#define STM_MASMSK0 0x0010
|
|
#define STM_MASMSK1 0x0018
|
|
#define STM_USBTO 0x0020
|
|
#define STM_CHMSK 0x0080
|
|
#define STM_AGTBAR0 0x00C0
|
|
#define STM_AGTBAR1 0x0140
|
|
#define STM_AGTBAR2 0x01C0
|
|
#define STM_AGTBAR3 0x0240
|
|
#define STM_AGTBAR4 0x02C0
|
|
#define STM_AGTBAR5 0x0340
|
|
#define STM_AGTBAR6 0x03C0
|
|
#define STM_AGTBAR7 0x0440
|
|
#define STM_AGTBAR8 0x04C0
|
|
#define STM_AGTBAR9 0x0540
|
|
#define STM_AGTBAR10 0x05C0
|
|
#define STM_AGTBAR11 0x0640
|
|
|
|
/*
|
|
* STM registers
|
|
*/
|
|
#define STM_REG_BASE 0x0 /* registers base offset */
|
|
#define STM_REG_LEN 0x20 /* address length */
|
|
/*
|
|
* TRB buffers
|
|
*/
|
|
#define STM_TRB_BASE 0x400 /* TRB base offset */
|
|
#define STM_TRB_LEN 0x100 /* address length */
|
|
#define STM_TRB_NUM 16 /* number of TRBs */
|
|
|
|
#define STM_MST_L_OFFSET(x) (STM_MASMSK0 + ((x)/32)*4)
|
|
#define STM_MST_L_MASK(x) (1<<((x)%32))
|
|
|
|
/*
|
|
* This protects R/W to stm registers
|
|
*/
|
|
static DEFINE_MUTEX(stmlock);
|
|
|
|
static struct stm_dev *_dev_stm;
|
|
|
|
static inline u32 stm_readl(void __iomem *base, u32 offset)
|
|
{
|
|
return readl(base + offset);
|
|
}
|
|
|
|
static inline void stm_writel(void __iomem *base, u32 offset, u32 value)
|
|
{
|
|
writel(value, base + offset);
|
|
}
|
|
|
|
struct master_list_entry {
|
|
u8 id;
|
|
bool disabled;
|
|
const char *short_name;
|
|
const char *long_name;
|
|
};
|
|
|
|
enum mids {
|
|
MID_PSH = 64,
|
|
MID_AUDIO,
|
|
MID_POWER,
|
|
MID_PUNIT,
|
|
MID_IAFW,
|
|
MID_SCU,
|
|
MID_CHAABI,
|
|
MID_MODEM,
|
|
};
|
|
|
|
static struct master_list_entry master_list[] = {
|
|
{MID_PSH, false, "psh", "PSH"},
|
|
{MID_AUDIO, false, "audio", "Audio"},
|
|
{MID_POWER, false, "power", "Power Management"},
|
|
{MID_PUNIT, false, "punit", "P-unit"},
|
|
{MID_IAFW, false, "iafw", "Core IA Firmware"},
|
|
{MID_SCU, false, "scu", "SCU Firmware"},
|
|
{MID_CHAABI, false, "chaabi", "Chaabi Security Engine"},
|
|
{MID_MODEM, false, "modem", "Modem Messages"},
|
|
|
|
/*This should always be last (loop untill el.short_name == NULL) */
|
|
{}
|
|
};
|
|
|
|
static u8 stm_set_master_output(u8 master_id, bool disable, bool update_table)
|
|
{
|
|
u32 reg;
|
|
/*basic out of bounds test */
|
|
if (master_id > 127)
|
|
return -1;
|
|
/*if the stm is not initialized yet, just update the table and make
|
|
* the changes when ready */
|
|
if (stm_is_enabled()) {
|
|
reg = stm_readl(_dev_stm->stm_ioaddr,
|
|
STM_MST_L_OFFSET(master_id));
|
|
|
|
if (disable)
|
|
reg |= STM_MST_L_MASK(master_id);
|
|
else
|
|
reg &= ~STM_MST_L_MASK(master_id);
|
|
|
|
stm_writel(_dev_stm->stm_ioaddr,
|
|
STM_MST_L_OFFSET(master_id), reg);
|
|
pr_debug("Set mid %d %d", master_id, disable);
|
|
}
|
|
if (update_table) {
|
|
struct master_list_entry *entr;
|
|
for (entr = master_list; entr->short_name; entr++) {
|
|
if (entr->id == master_id)
|
|
entr->disabled = disable;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* stm_kernel_set_out()-
|
|
* Kernel API function used to
|
|
* set STM output configuration to PTI or USB.
|
|
*
|
|
* @bus_type:
|
|
* 0 = PTI 4-bits legacy end user
|
|
* 1 = PTI 4-bits NiDnT
|
|
* 2 = PTI 16-bits
|
|
* 3 = PTI 12-bits
|
|
* 4 = PTI 8-bits
|
|
* 15 = USB Debug-Class (DvC.Trace)
|
|
*
|
|
*/
|
|
int stm_kernel_set_out(int bus_type)
|
|
{
|
|
|
|
struct stm_dev *drv_stm = _dev_stm;
|
|
|
|
/*
|
|
* since this function is exported, this is treated like an
|
|
* API function, thus, all parameters should
|
|
* be checked for validity.
|
|
*/
|
|
if (drv_stm == NULL)
|
|
return 0;
|
|
|
|
mutex_lock(&stmlock);
|
|
|
|
drv_stm->stm_ctrl_hwreg.reg_word =
|
|
stm_readl(drv_stm->stm_ioaddr, (u32)STM_CTRL);
|
|
|
|
switch (bus_type) {
|
|
case STM_PTI_4BIT_LEGACY:
|
|
case STM_PTI_4BIT_NIDNT:
|
|
case STM_PTI_16BIT:
|
|
case STM_PTI_12BIT:
|
|
case STM_PTI_8BIT:
|
|
drv_stm->stm_ctrl_hwreg.pti_out_en = true;
|
|
drv_stm->stm_ctrl_hwreg.usb_debug_en = false;
|
|
drv_stm->stm_ctrl_hwreg.pti_out_mode_sel = bus_type;
|
|
stm_writel(drv_stm->stm_ioaddr, (u32)STM_CTRL,
|
|
drv_stm->stm_ctrl_hwreg.reg_word);
|
|
break;
|
|
case STM_USB:
|
|
drv_stm->stm_ctrl_hwreg.pti_out_en = false;
|
|
drv_stm->stm_ctrl_hwreg.usb_debug_en = true;
|
|
stm_writel(drv_stm->stm_ioaddr, (u32)STM_CTRL,
|
|
drv_stm->stm_ctrl_hwreg.reg_word);
|
|
break;
|
|
default:
|
|
/* N/A */
|
|
break;
|
|
}
|
|
mutex_unlock(&stmlock);
|
|
|
|
return 1;
|
|
}
|
|
EXPORT_SYMBOL_GPL(stm_kernel_set_out);
|
|
|
|
/**
|
|
* stm_kernel_get_out()-
|
|
* Kernel API function used to get
|
|
* STM output cofiguration PTI or USB.
|
|
*
|
|
*/
|
|
int stm_kernel_get_out(void)
|
|
{
|
|
struct stm_dev *drv_stm = _dev_stm;
|
|
int ret = -EOPNOTSUPP;
|
|
|
|
if (drv_stm == NULL)
|
|
return -EOPNOTSUPP;
|
|
|
|
mutex_lock(&stmlock);
|
|
|
|
drv_stm->stm_ctrl_hwreg.reg_word =
|
|
stm_readl(drv_stm->stm_ioaddr, (u32)STM_CTRL);
|
|
|
|
if (!drv_stm->stm_ctrl_hwreg.usb_debug_en) {
|
|
if (drv_stm->stm_ctrl_hwreg.pti_out_en)
|
|
ret = (int)drv_stm->stm_ctrl_hwreg.pti_out_mode_sel;
|
|
} else {
|
|
ret = (int)STM_USB;
|
|
}
|
|
mutex_unlock(&stmlock);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(stm_kernel_get_out);
|
|
|
|
/**
|
|
* stm_set_out() - 'out' parameter set function from 'STM' module
|
|
*
|
|
* called when writing to 'out' parameter from 'STM' module in sysfs
|
|
*/
|
|
static int stm_set_out(const char *val, struct kernel_param *kp)
|
|
{
|
|
int bus_type_value;
|
|
int ret = -EINVAL;
|
|
|
|
if (sscanf(val, "%2d", &bus_type_value) != 1)
|
|
return ret;
|
|
|
|
return stm_kernel_set_out(bus_type_value);
|
|
}
|
|
|
|
/**
|
|
* stm_get_out() - 'out' parameter get function from 'STM' module
|
|
*
|
|
* called when reading 'out' parameter from 'STM' module in sysfs
|
|
*/
|
|
static int stm_get_out(char *buffer, struct kernel_param *kp)
|
|
{
|
|
int i;
|
|
|
|
i = stm_kernel_get_out();
|
|
if (i == -EOPNOTSUPP) {
|
|
buffer[0] = '\0';
|
|
return 0;
|
|
}
|
|
|
|
return sprintf(buffer, "%2d", i);
|
|
}
|
|
|
|
/**
|
|
* stm_init() - initialize stmsub3dbgthr register
|
|
*
|
|
* @return - 0 on Success
|
|
*/
|
|
static int stm_init(void)
|
|
{
|
|
struct stm_dev *stm = _dev_stm;
|
|
struct stm_usb3_ctrl *usb3dbg;
|
|
|
|
if (!stm)
|
|
return -ENODEV;
|
|
|
|
usb3dbg = &stm->stm_usb3_hwreg;
|
|
usb3dbg->reg_word = stm_readl(stm->stm_ioaddr, (u32)STM_USB3DBGGTHR);
|
|
|
|
usb3dbg->reg_word = 0xFF;
|
|
|
|
stm_writel(stm->stm_ioaddr, (u32)STM_USB3DBGGTHR, usb3dbg->reg_word);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* stm_alloc_static_trb_pool() - set stm trb pool dma_addr and return
|
|
* trb_pool
|
|
*
|
|
* @dma_addr - trb pool dma physical address to set
|
|
* @return - trb pool address ioremaped pointer
|
|
*/
|
|
static void *stm_alloc_static_trb_pool(dma_addr_t *dma_addr)
|
|
{
|
|
struct stm_dev *stm = _dev_stm;
|
|
if (!stm)
|
|
return NULL;
|
|
|
|
*dma_addr = stm->stm_trb_base;
|
|
return stm->trb_ioaddr;
|
|
}
|
|
|
|
static void ebc_io_free_static_trb_pool(void)
|
|
{
|
|
/* Nothing to do, HW TRB */
|
|
}
|
|
|
|
static int stm_xfer_start(void)
|
|
{
|
|
struct stm_dev *stm = _dev_stm;
|
|
struct stm_ctrl *stm_ctrl;
|
|
u32 reg_word;
|
|
|
|
if (!stm)
|
|
return -ENODEV;
|
|
|
|
/* REVERTME : filter PUNIT and SCU MasterID when switching to USB */
|
|
if (intel_mid_identify_cpu() == INTEL_MID_CPU_CHIP_ANNIEDALE) {
|
|
pr_info("%s\n REVERTME : USBTO\n", __func__);
|
|
reg_word = stm_readl(stm->stm_ioaddr, (u32)STM_USBTO);
|
|
reg_word |= 0x01;
|
|
stm_writel(stm->stm_ioaddr, (u32)STM_USBTO, reg_word);
|
|
}
|
|
|
|
stm_ctrl = &stm->stm_ctrl_hwreg;
|
|
stm_ctrl->reg_word = stm_readl(stm->stm_ioaddr, (u32)STM_CTRL);
|
|
|
|
stm_ctrl->usb_debug_en = true;
|
|
stm_ctrl->pti_out_en = false;
|
|
|
|
stm_writel(stm->stm_ioaddr, (u32)STM_CTRL, stm_ctrl->reg_word);
|
|
pr_info("%s\n switch STM output to DvC.Trace ", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int stm_xfer_stop(void)
|
|
{
|
|
struct stm_dev *stm = _dev_stm;
|
|
struct stm_ctrl *stm_ctrl;
|
|
|
|
if (!stm)
|
|
return -ENODEV;
|
|
|
|
stm_ctrl = &stm->stm_ctrl_hwreg;
|
|
stm_ctrl->reg_word = stm_readl(stm->stm_ioaddr, (u32)STM_CTRL);
|
|
|
|
stm_ctrl->usb_debug_en = false;
|
|
stm_ctrl->pti_out_en = true;
|
|
|
|
stm_writel(stm->stm_ioaddr, (u32)STM_CTRL, stm_ctrl->reg_word);
|
|
pr_info("%s\n switch STM to 4bits MIPI PTI (default)", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct ebc_io stm_ebc_io_ops = {
|
|
.name = "stmbuf4kB",
|
|
.epname = "ep1in",
|
|
.epnum = 3,
|
|
.is_ondemand = 1,
|
|
.static_trb_pool_size = 4,
|
|
.init = stm_init,
|
|
.alloc_static_trb_pool = stm_alloc_static_trb_pool,
|
|
.free_static_trb_pool = ebc_io_free_static_trb_pool,
|
|
.xfer_start = stm_xfer_start,
|
|
.xfer_stop = stm_xfer_stop,
|
|
};
|
|
|
|
#define EXI_IN_TRB_POOL_OFFSET (4*16)
|
|
static void *exi_inbound_alloc_static_trb_pool(dma_addr_t *dma_addr)
|
|
{
|
|
struct stm_dev *stm = _dev_stm;
|
|
if (!stm)
|
|
return NULL;
|
|
|
|
*dma_addr = stm->stm_trb_base + EXI_IN_TRB_POOL_OFFSET;
|
|
return stm->trb_ioaddr + EXI_IN_TRB_POOL_OFFSET;
|
|
}
|
|
|
|
static struct ebc_io exi_in_ebc_io_ops = {
|
|
.name = "exi-inbound",
|
|
.epname = "ep8in",
|
|
.epnum = 17,
|
|
.is_ondemand = 0,
|
|
.static_trb_pool_size = 4,
|
|
.alloc_static_trb_pool = exi_inbound_alloc_static_trb_pool,
|
|
.free_static_trb_pool = ebc_io_free_static_trb_pool,
|
|
};
|
|
|
|
#define EXI_OUT_TRB_POOL_OFFSET (8*16)
|
|
static void *exi_outbound_alloc_static_trb_pool(dma_addr_t *dma_addr)
|
|
{
|
|
struct stm_dev *stm = _dev_stm;
|
|
if (!stm)
|
|
return NULL;
|
|
|
|
*dma_addr = stm->stm_trb_base + EXI_OUT_TRB_POOL_OFFSET;
|
|
return stm->trb_ioaddr + EXI_OUT_TRB_POOL_OFFSET;
|
|
}
|
|
|
|
static struct ebc_io exi_out_ebc_io_ops = {
|
|
.name = "exi-outbound",
|
|
.epname = "ep8out",
|
|
.epnum = 16,
|
|
.is_ondemand = 0,
|
|
.static_trb_pool_size = 2,
|
|
.alloc_static_trb_pool = exi_outbound_alloc_static_trb_pool,
|
|
.free_static_trb_pool = ebc_io_free_static_trb_pool,
|
|
};
|
|
|
|
int stm_is_enabled()
|
|
{
|
|
return (_dev_stm != NULL);
|
|
}
|
|
EXPORT_SYMBOL_GPL(stm_is_enabled);
|
|
|
|
/**
|
|
* stm_dev_init()- Used to setup STM resources on the pci bus.
|
|
*
|
|
* @pdev- pci_dev struct values for pti device.
|
|
* @stm- stm_dev struct managing stm resources
|
|
*
|
|
* Returns:
|
|
* 0 for success
|
|
* otherwise, error
|
|
*/
|
|
int stm_dev_init(struct pci_dev *pdev,
|
|
struct stm_dev *stm)
|
|
{
|
|
int retval = 0;
|
|
int pci_bar = 0;
|
|
struct master_list_entry *entr;
|
|
|
|
if (!cpu_has_debug_feature(DEBUG_FEATURE_PTI))
|
|
return -ENODEV;
|
|
|
|
dev_dbg(&pdev->dev, "%s %s(%d): STM PCI ID %04x:%04x\n", __FILE__,
|
|
__func__, __LINE__, pdev->vendor, pdev->device);
|
|
|
|
stm->stm_addr = pci_resource_start(pdev, pci_bar);
|
|
|
|
retval = pci_request_region(pdev, pci_bar, dev_name(&pdev->dev));
|
|
if (retval != 0) {
|
|
dev_err(&pdev->dev,
|
|
"%s(%d): pci_request_region() returned error %d\n",
|
|
__func__, __LINE__, retval);
|
|
return retval;
|
|
}
|
|
pr_info("stm add %x\n", stm->stm_addr);
|
|
|
|
stm->stm_reg_base = stm->stm_addr+STM_REG_BASE;
|
|
stm->stm_ioaddr = ioremap_nocache((u32)stm->stm_reg_base,
|
|
STM_REG_LEN);
|
|
if (!stm->stm_ioaddr) {
|
|
retval = -ENOMEM;
|
|
goto out_release_region;
|
|
}
|
|
|
|
stm->stm_trb_base = stm->stm_addr+STM_TRB_BASE;
|
|
stm->trb_ioaddr = ioremap_nocache((u32)stm->stm_trb_base,
|
|
STM_TRB_LEN);
|
|
if (!stm->trb_ioaddr) {
|
|
retval = -ENOMEM;
|
|
goto out_iounmap_stm_ioaddr;
|
|
}
|
|
|
|
stm->stm_ctrl_hwreg.reg_word = stm_readl(stm->stm_ioaddr,
|
|
(u32)STM_CTRL);
|
|
stm->stm_usb3_hwreg.reg_word = stm_readl(stm->stm_ioaddr,
|
|
(u32)STM_USB3DBGGTHR);
|
|
|
|
_dev_stm = stm;
|
|
|
|
dwc3_register_io_ebc(&stm_ebc_io_ops);
|
|
dwc3_register_io_ebc(&exi_in_ebc_io_ops);
|
|
dwc3_register_io_ebc(&exi_out_ebc_io_ops);
|
|
|
|
/*setup the mid filter */
|
|
for (entr = master_list; entr->short_name; entr++) {
|
|
if (entr->disabled)
|
|
stm_set_master_output(entr->id, true, false);
|
|
}
|
|
|
|
pr_info("successfully registered ebc io ops\n");
|
|
|
|
return retval;
|
|
|
|
out_iounmap_stm_ioaddr:
|
|
pci_iounmap(pdev, stm->stm_ioaddr);
|
|
|
|
out_release_region:
|
|
pci_release_region(pdev, pci_bar);
|
|
|
|
_dev_stm = NULL;
|
|
return retval;
|
|
|
|
}
|
|
EXPORT_SYMBOL_GPL(stm_dev_init);
|
|
|
|
/**
|
|
* stm_dev_clean()- Driver exit method to free STM resources from
|
|
* PCI bus.
|
|
* @pdev: variable containing pci info of STM.
|
|
* @dev_stm: stm_dev resources to clean.
|
|
*/
|
|
void stm_dev_clean(struct pci_dev *pdev,
|
|
struct stm_dev *dev_stm)
|
|
{
|
|
int pci_bar = 0;
|
|
|
|
/* If STM driver was not initialized properly,
|
|
* there is nothing to do.
|
|
*/
|
|
if (_dev_stm == NULL)
|
|
return;
|
|
|
|
dwc3_unregister_io_ebc(&stm_ebc_io_ops);
|
|
dwc3_unregister_io_ebc(&exi_in_ebc_io_ops);
|
|
dwc3_unregister_io_ebc(&exi_out_ebc_io_ops);
|
|
|
|
if (dev_stm != NULL) {
|
|
pci_iounmap(pdev, dev_stm->stm_ioaddr);
|
|
pci_iounmap(pdev, dev_stm->trb_ioaddr);
|
|
}
|
|
|
|
pci_release_region(pdev, pci_bar);
|
|
|
|
_dev_stm = NULL;
|
|
}
|
|
EXPORT_SYMBOL_GPL(stm_dev_clean);
|
|
|
|
module_param_call(stm_out, stm_set_out, stm_get_out, NULL, 0644);
|
|
MODULE_PARM_DESC(stm_out, "configure System Trace Macro output");
|
|
|
|
static int mid_disabled_set(const char *val, const struct kernel_param *kp)
|
|
{
|
|
struct master_list_entry *entr;
|
|
|
|
for (entr = master_list; entr->short_name; entr++) {
|
|
if (strstr(val, entr->short_name)) {
|
|
if (!entr->disabled) {
|
|
pr_info("Disable %s (%s) master output",
|
|
entr->long_name, entr->short_name);
|
|
entr->disabled = true;
|
|
stm_set_master_output(entr->id,
|
|
entr->disabled, false);
|
|
}
|
|
} else {
|
|
if (entr->disabled) {
|
|
pr_info("Enable %s (%s) master output",
|
|
entr->long_name, entr->short_name);
|
|
entr->disabled = false;
|
|
stm_set_master_output(entr->id,
|
|
entr->disabled, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mid_disabled_get(char *buffer, const struct kernel_param *kp)
|
|
{
|
|
char *current_b = buffer;
|
|
struct master_list_entry *entr;
|
|
|
|
for (entr = master_list; entr->short_name; entr++) {
|
|
if (entr->disabled)
|
|
current_b += sprintf(current_b, "%s,",
|
|
entr->short_name);
|
|
}
|
|
return strlen(buffer);
|
|
}
|
|
|
|
static struct kernel_param_ops mid_disabled_ops = {
|
|
.set = mid_disabled_set,
|
|
.get = mid_disabled_get,
|
|
};
|
|
|
|
module_param_cb(mid_disabled, &mid_disabled_ops, NULL, 0644);
|
|
MODULE_PARM_DESC(mid_disabled, "Disable Stm output for a list of masters");
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Florent Pirou");
|
|
MODULE_DESCRIPTION("STM Driver");
|