android_kernel_lenovo_1050f/drivers/misc/stm.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");