/* * 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 #include #include #include #include #include "stm.h" #include #include #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");