blob: a229d2011da123815793c26f48745bc76002ee55 [file] [log] [blame]
/*
* Copyright (c) 2018 MediaTek Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include "xhci.h"
#include "xhci-mtk.h"
#include "mtu3.h"
#include "mtu3_hal.h"
static int dpidle_hs_max = -1;
module_param(dpidle_hs_max, int, 0644);
MODULE_PARM_DESC(dpidle_hs_max, "MAX dpidle time for HS UAC");
static int dpidle_fs_max = -1;
module_param(dpidle_fs_max, int, 0644);
MODULE_PARM_DESC(dpidle_fs_max, "MAX dpidle time for FS UAC");
static int dpidle_min = 10; /* default is 10 ms */
module_param(dpidle_min, int, 0644);
MODULE_PARM_DESC(dpidle_min, "MIN dpidle time for UAC");
static struct xhci_mtk_sram_block xhci_sram[XHCI_SRAM_BLOCK_NUM] = {
[XHCI_EVENTRING] = {0, NULL, TRB_SEGMENT_SIZE, STATE_UNINIT},
[XHCI_EPTX] = {0, NULL, TRB_SEGMENT_SIZE, STATE_UNINIT},
[XHCI_EPRX] = {0, NULL, TRB_SEGMENT_SIZE, STATE_UNINIT},
/* add 56 bytes for alignment */
[XHCI_DCBAA] = {0, NULL, 8 * MAX_HC_SLOTS + 8 + 56, STATE_UNINIT},
/* add 48 bytes for alignment */
[XHCI_ERST] = {0, NULL, 16 * ERST_NUM_SEGS + 48, STATE_UNINIT}
};
static struct xhci_mtk_sram_block usb_audio_sram[USB_AUDIO_DATA_BLOCK_NUM] = {
[USB_AUDIO_DATA_OUT_EP] = {0, NULL, 0, STATE_UNINIT},
[USB_AUDIO_DATA_IN_EP] = {0, NULL, 0, STATE_UNINIT},
[USB_AUDIO_DATA_SYNC_EP] = {0, NULL, 0, STATE_UNINIT}
};
static atomic_t power_status = ATOMIC_INIT(USB_DPIDLE_FORBIDDEN);
static int xhci_mtk_set_power_mode(int mode)
{
/* TODO */
if (atomic_read(&power_status) != mode) {
ssusb_dpidle_request(mode);
atomic_set(&power_status, mode);
}
return 0;
}
static void xhci_mtk_wakeup_timer_func(unsigned long data)
{
xhci_mtk_set_power_mode(USB_DPIDLE_FORBIDDEN);
}
static DEFINE_TIMER(xhci_wakeup_timer, xhci_mtk_wakeup_timer_func,
0, 0);
void xhci_mtk_allow_sleep(unsigned int idle_ms, int speed)
{
int i;
bool data_sram = false;
unsigned int sleep_ms = 0;
switch (speed) {
case USB_SPEED_SUPER:
case USB_SPEED_HIGH:
sleep_ms = idle_ms * 2 / 3 / 8;
if (dpidle_hs_max >= 0 && sleep_ms > dpidle_hs_max)
sleep_ms = dpidle_hs_max;
break;
case USB_SPEED_FULL:
case USB_SPEED_LOW:
default:
sleep_ms = idle_ms * 3 / 4;
if (dpidle_fs_max >= 0 && sleep_ms > dpidle_fs_max)
sleep_ms = dpidle_fs_max;
break;
}
/* step1: check if have enough sleep time */
if (unlikely(sleep_ms <= dpidle_min))
goto not_sleep;
/* step2: check if xhci state is normal */
for (i = 0; i < XHCI_SRAM_BLOCK_NUM; i++)
if (unlikely(xhci_sram[i].state == STATE_NOMEM))
goto not_sleep;
/* setp3: check if usb audio data on sram */
for (i = 0; i < USB_AUDIO_DATA_BLOCK_NUM; i++) {
if (unlikely(usb_audio_sram[i].state == STATE_NOMEM))
goto not_sleep;
else if (usb_audio_sram[i].state == STATE_USE)
data_sram = true;
}
if (likely(data_sram)) {
static DEFINE_RATELIMIT_STATE(ratelimit, 2 * HZ, 1);
if (__ratelimit(&ratelimit))
pr_info("mtk_xhci_allow_sleep (%d) ms\n", sleep_ms);
mod_timer(&xhci_wakeup_timer,
jiffies + msecs_to_jiffies(sleep_ms));
xhci_mtk_set_power_mode(USB_DPIDLE_SRAM);
}
return;
not_sleep:
xhci_mtk_set_power_mode(USB_DPIDLE_FORBIDDEN);
}
EXPORT_SYMBOL_GPL(xhci_mtk_allow_sleep);
void xhci_mtk_set_sleep(bool enable)
{
if (enable)
xhci_mtk_set_power_mode(USB_DPIDLE_SRAM);
else
xhci_mtk_set_power_mode(USB_DPIDLE_FORBIDDEN);
}
EXPORT_SYMBOL_GPL(xhci_mtk_set_sleep);
int xhci_mtk_init_sram(struct xhci_hcd *xhci)
{
int i;
int offset = 0;
unsigned int xhci_sram_size = 0;
/* init xhci sram */
for (i = 0; i < XHCI_SRAM_BLOCK_NUM; i++)
xhci_sram_size += xhci_sram[i].mlength;
pr_info("%s size=%d\n", __func__, xhci_sram_size);
if (mtk_audio_request_sram(&xhci->msram_phys_addr,
(unsigned char **) &xhci->msram_virt_addr,
xhci_sram_size, &xhci_sram)) {
xhci->msram_virt_addr = NULL;
for (i = 0; i < XHCI_SRAM_BLOCK_NUM; i++)
xhci_sram[i].state = STATE_NOMEM;
pr_info("mtk_audio_request_sram fail\n");
return -ENOMEM;
}
for (i = 0; i < XHCI_SRAM_BLOCK_NUM; i++) {
xhci_sram[i].msram_phys_addr =
xhci->msram_phys_addr + offset;
xhci_sram[i].msram_virt_addr =
(void *)((char *)xhci->msram_virt_addr + offset);
offset += xhci_sram[i].mlength;
memset_io(xhci_sram[i].msram_virt_addr,
0, xhci_sram[i].mlength);
xhci_sram[i].state = STATE_INIT;
pr_debug("[%d] p :%llx, v=%p, len=%d\n",
i, xhci_sram[i].msram_phys_addr,
xhci_sram[i].msram_virt_addr,
xhci_sram[i].mlength);
}
return 0;
}
EXPORT_SYMBOL_GPL(xhci_mtk_init_sram);
int xhci_mtk_deinit_sram(struct xhci_hcd *xhci)
{
int i;
if (xhci->msram_virt_addr)
mtk_audio_free_sram(&xhci_sram);
for (i = 0; i < XHCI_SRAM_BLOCK_NUM; i++) {
xhci_sram[i].msram_phys_addr = 0;
xhci_sram[i].msram_virt_addr = NULL;
xhci_sram[i].state = STATE_UNINIT;
}
xhci->msram_virt_addr = NULL;
pr_info("%s\n", __func__);
return 0;
}
EXPORT_SYMBOL_GPL(xhci_mtk_deinit_sram);
int xhci_mtk_allocate_sram(unsigned int id, dma_addr_t *sram_phys_addr,
unsigned char **msram_virt_addr)
{
if (xhci_sram[id].state == STATE_NOMEM)
return -ENOMEM;
if (xhci_sram[id].state == STATE_USE) {
/* use the same sram block, set state to nomem*/
xhci_sram[id].state = STATE_NOMEM;
return -ENOMEM;
}
*sram_phys_addr = xhci_sram[id].msram_phys_addr;
*msram_virt_addr =
(unsigned char *)xhci_sram[id].msram_virt_addr;
memset_io(xhci_sram[id].msram_virt_addr,
0, xhci_sram[id].mlength);
xhci_sram[id].state = STATE_USE;
pr_info("%s get [%d] p :%llx, v=%p, len=%d\n",
__func__, id, xhci_sram[id].msram_phys_addr,
xhci_sram[id].msram_virt_addr,
xhci_sram[id].mlength);
return 0;
}
EXPORT_SYMBOL_GPL(xhci_mtk_allocate_sram);
int xhci_mtk_free_sram(unsigned int id)
{
xhci_sram[id].state = STATE_INIT;
pr_info("%s, id=%d\n", __func__, id);
return 0;
}
EXPORT_SYMBOL_GPL(xhci_mtk_free_sram);
void *mtk_usb_alloc_sram(unsigned int id, size_t size, dma_addr_t *dma)
{
void *sram_virt_addr = NULL;
/* check if xhci control buffer on sram */
if (xhci_sram[0].state != STATE_USE)
return NULL;
mtk_audio_request_sram(dma, (unsigned char **)&sram_virt_addr,
size, &usb_audio_sram[id]);
if (sram_virt_addr) {
usb_audio_sram[id].mlength = size;
usb_audio_sram[id].msram_phys_addr = *dma;
usb_audio_sram[id].msram_virt_addr = sram_virt_addr;
usb_audio_sram[id].state = STATE_USE;
pr_debug("%s, id=%d\n", __func__, id);
} else {
usb_audio_sram[id].state = STATE_NOMEM;
pr_info("%s fail id=%d\n", __func__, id);
}
return sram_virt_addr;
}
EXPORT_SYMBOL_GPL(mtk_usb_alloc_sram);
void mtk_usb_free_sram(unsigned int id)
{
if (usb_audio_sram[id].state == STATE_USE) {
mtk_audio_free_sram(&usb_audio_sram[id]);
usb_audio_sram[id].mlength = 0;
usb_audio_sram[id].msram_phys_addr = 0;
usb_audio_sram[id].msram_virt_addr = NULL;
pr_debug("%s, id=%d\n", __func__, id);
} else {
pr_info("%s, fail id=%d\n", __func__, id);
}
usb_audio_sram[id].state = STATE_UNINIT;
}
EXPORT_SYMBOL_GPL(mtk_usb_free_sram);