blob: 50b7427f41a9afe2c9897527ffa4cfa0f6f64246 [file] [log] [blame]
/* linux/drivers/modem/modem.c
*
* Copyright (C) 2010 Google, Inc.
* Copyright (C) 2010 Samsung Electronics.
*
* 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/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/if_arp.h>
#include <linux/version.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/wakelock.h>
#ifdef CONFIG_OF
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_reserved_mem.h>
#endif
#include <linux/pci.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/pm_runtime.h>
#include <linux/exynos-pci-ctrl.h>
#include <linux/exynos-pci-noti.h>
#include "modem_prj.h"
#include "modem_utils.h"
#include "link_device_memory.h"
#include "s5100_pcie.h"
struct s5100pcie s5100pcie;
void quirk_s350_set_atu1(struct pci_dev *dev);
static int s5100pcie_read_procmem(struct seq_file *m, void *v)
{
pr_err("Procmem READ!\n");
return 0;
}
static int s5100pcie_proc_open(struct inode *inode, struct file *file) {
return single_open(file, s5100pcie_read_procmem, NULL);
}
static const struct file_operations s5100pcie_proc_fops = {
.owner = THIS_MODULE,
.open = s5100pcie_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
inline int s5100pcie_send_doorbell_int(int int_num)
{
u32 reg, count = 0;
int cnt = 0;
u16 cmd;
s5100_check_doorbell_ready();
if (s5100pcie.link_status == 0) {
mif_err_limited("Can't send Interrupt(not enabled)!!!\n");
return -EAGAIN;
}
if (exynos_check_pcie_link_status(s5100pcie.pcie_channel_num) == 0) {
mif_err_limited("Can't send Interrupt(not linked)!!!\n");
return -EAGAIN;
}
pci_read_config_word(s5100pcie.s5100_pdev, PCI_COMMAND, &cmd);
if (((cmd & PCI_COMMAND_MASTER) == 0) || (cmd == 0xffff)) {
mif_err_limited("Can't send Interrupt(not setted bme_en, 0x%04x)!!!\n", cmd);
do {
cnt++;
pci_set_master(s5100pcie.s5100_pdev);
pci_read_config_word(s5100pcie.s5100_pdev, PCI_COMMAND, &cmd);
mif_info("cmd reg = 0x%04x \n", cmd);
if ((cmd & PCI_COMMAND_MASTER) && (cmd != 0xffff))
break;
} while (cnt < 10);
if (cnt >= 10) {
mif_err_limited("BME is not set(cnt=%d)\n", cnt);
return -EAGAIN;
}
}
send_doorbell_again:
iowrite32(int_num, s5100pcie.doorbell_addr);
reg = ioread32(s5100pcie.doorbell_addr);
/* debugging: */
mif_debug("DBG: s5100pcie.doorbell_addr = 0x%x - written(int_num=0x%x) read(reg=0x%x)\n", \
s5100pcie.doorbell_addr, int_num, reg);
if (reg == 0xffffffff) {
count++;
if (count < 100) {
if (!in_interrupt())
udelay(1000); /* 1ms */
else {
mif_err_limited("Can't send doorbell in interrupt mode (0x%08X)\n"
, reg);
return 0;
}
#ifdef CONFIG_LTE_MODEM_S5100
mif_err("retry set atu cnt = %d\n", count);
quirk_s350_set_atu1(s5100pcie.s5100_pdev);
#endif
goto send_doorbell_again;
}
mif_err("[Need to CHECK] Can't send doorbell int (0x%x)\n", reg);
exynos_pcie_host_v1_register_dump(s5100pcie.pcie_channel_num);
return -EAGAIN;
}
return 0;
}
struct pci_dev *s5100pcie_get_pcidev(void)
{
if (s5100pcie.s5100_pdev != NULL) {
return s5100pcie.s5100_pdev;
} else {
pr_err("PCI device is NOT initialzed!!!\n");
return NULL;
}
}
void s5100pcie_set_cp_wake_gpio(int cp_wakeup)
{
s5100pcie.gpio_cp_wakeup = cp_wakeup;
}
void save_s5100_status()
{
if (exynos_check_pcie_link_status(s5100pcie.pcie_channel_num) == 0) {
mif_err("It's not Linked - Ignore saving the s5100\n");
return;
}
/* pci_pme_active(s5100pcie.s5100_pdev, 0); */
/* Disable L1.2 before PCIe power off */
/* exynos_pcie_host_v1_l1ss_ctrl(0, PCIE_L1SS_CTRL_MODEM_IF); */
pci_clear_master(s5100pcie.s5100_pdev);
pci_save_state(s5100pcie.s5100_pdev);
s5100pcie.pci_saved_configs = pci_store_saved_state(s5100pcie.s5100_pdev);
disable_msi_int();
/* pci_enable_wake(s5100pcie.s5100_pdev, PCI_D0, 0); */
pci_disable_device(s5100pcie.s5100_pdev);
pci_wake_from_d3(s5100pcie.s5100_pdev, false);
if (pci_set_power_state(s5100pcie.s5100_pdev, PCI_D3hot)) {
mif_err("Can't set D3 state!!!!\n");
}
}
void restore_s5100_state()
{
int ret;
if (exynos_check_pcie_link_status(s5100pcie.pcie_channel_num) == 0) {
mif_err("It's not Linked - Ignore restoring the s5100_status!\n");
return;
}
if (pci_set_power_state(s5100pcie.s5100_pdev, PCI_D0)) {
mif_err("Can't set D0 state!!!!\n");
}
pci_load_saved_state(s5100pcie.s5100_pdev, s5100pcie.pci_saved_configs);
pci_restore_state(s5100pcie.s5100_pdev);
pci_enable_wake(s5100pcie.s5100_pdev, PCI_D0, 0);
/* pci_enable_wake(s5100pcie.s5100_pdev, PCI_D3hot, 0); */
ret = pci_enable_device(s5100pcie.s5100_pdev);
if (ret) {
pr_err("Can't enable PCIe Device after linkup!\n");
}
pci_set_master(s5100pcie.s5100_pdev);
#ifdef CONFIG_DISABLE_PCIE_CP_L1_2
/* Disable L1.2 after PCIe power on */
exynos_pcie_host_v1_l1ss_ctrl(0, PCIE_L1SS_CTRL_MODEM_IF);
#else
/* Enable L1.2 after PCIe power on */
exynos_pcie_host_v1_l1ss_ctrl(1, PCIE_L1SS_CTRL_MODEM_IF);
#endif
s5100pcie.link_status = 1;
/* pci_pme_active(s5100pcie.s5100_pdev, 1); */
}
void disable_msi_int()
{
s5100pcie.link_status = 0;
/* It's not needed now...
* pci_disable_msi(s5100pcie.s5100_pdev);
* pci_config_pm_runtime_put(&s5100pcie.s5100_pdev->dev);
*/
}
int s5100pcie_request_msi_int(int int_num)
{
int err = -EFAULT;
pr_err("Enable MSI interrupts : %d\n", int_num);
if (int_num > MAX_MSI_NUM) {
pr_err("Too many MSI interrupts are requested(<=16)!!!\n");
return -EFAULT;
}
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,18,0))
/* used 'pci_enable_msi_range()' before kernel 4.14 ver.:
err = __pci_enable_msi_range(s5100pcie.s5100_pdev, int_num, int_num); */
err = pci_alloc_irq_vectors_affinity(s5100pcie.s5100_pdev, int_num, int_num, PCI_IRQ_MSI, NULL);
#else
err = pci_enable_msi_block(s5100pcie.s5100_pdev, int_num);
#endif
if (err <= 0) {
printk("Can't get msi IRQ!!!!!\n");
return -EFAULT;
}
return s5100pcie.s5100_pdev->irq;
}
extern int s5100_force_crash_exit_ext(void);
static void s5100pcie_linkdown_cb(struct exynos_pcie_notify *noti)
{
struct pci_dev __maybe_unused *pdev = (struct pci_dev *)noti->user;
pr_err("S5100 Link-Down notification callback function!!!\n");
s5100_force_crash_exit_ext();
}
static int s5100pcie_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
int ret;
int __maybe_unused i;
pr_info("S5100 EP driver Probe(%s)\n", __func__);
s5100pcie.s5100_pdev = pdev;
s5100pcie.irq_num_base = pdev->irq;
s5100pcie.link_status = 1;
pr_err("Disable BAR resources.\n");
for (i = 0; i < 6; i++) {
pdev->resource[i].start = 0x0;
pdev->resource[i].end = 0x0;
pdev->resource[i].flags = 0x82000000;
pci_update_resource(pdev, i);
}
/* EP(S5100) BAR setup: BAR0 0x12d0_0000~0x12d0_0fff (4kB) */
pdev->resource[0].start = 0x12d00000;
pdev->resource[0].end = (0x12d00000 + 0xfff);
pdev->resource[0].flags = 0x82000000;
pci_update_resource(pdev, 0);
pr_err("Set Doorbell register addres.\n");
#if defined(CONFIG_SOC_EXYNOS8890)
if (s5100pcie.pcie_channel_num == 0)
s5100pcie.doorbell_addr = devm_ioremap_wc(&pdev->dev,
0x1c002c20, SZ_4);
else
s5100pcie.doorbell_addr = devm_ioremap_wc(&pdev->dev,
0x1e002c20, SZ_4);
#elif defined(CONFIG_SOC_EXYNOS8895)
if (s5100pcie.pcie_channel_num == 0)
s5100pcie.doorbell_addr = devm_ioremap_wc(&pdev->dev,
0x11802c20, SZ_4);
else
s5100pcie.doorbell_addr = devm_ioremap_wc(&pdev->dev,
0x11c02c20, SZ_4);
#elif defined(CONFIG_SOC_EXYNOS9820)
/* doorbell target address: (RC)0x1100_0d20 ->(EP)0x12d0_0d20 */
if (s5100pcie.pcie_channel_num == 0)
s5100pcie.doorbell_addr = devm_ioremap_wc(&pdev->dev,
0x0, SZ_4); /* ch0: TBD */
else
s5100pcie.doorbell_addr = devm_ioremap_wc(&pdev->dev,
0x11000d20, SZ_4);
pr_info("s5100pcie.doorbell_addr = 0x%x (CONFIG_SOC_EXYNOS9820: 0x11000d20)\n", \
s5100pcie.doorbell_addr);
#else
#error "Can't set Doorbell interrupt register!"
#endif
if (s5100pcie.doorbell_addr == NULL)
pr_err("Can't ioremap doorbell address!!!\n");
pr_info("Register PCIE notification event...\n");
s5100pcie.pcie_event.events = EXYNOS_PCIE_EVENT_LINKDOWN;
s5100pcie.pcie_event.user = pdev;
s5100pcie.pcie_event.mode = EXYNOS_PCIE_TRIGGER_CALLBACK;
s5100pcie.pcie_event.callback = s5100pcie_linkdown_cb;
exynos_pcie_host_v1_register_event(&s5100pcie.pcie_event);
pr_err("Enable PCI device...\n");
ret = pci_enable_device(pdev);
pci_set_master(pdev);
pci_set_drvdata(pdev, &s5100pcie);
return 0;
}
void print_msi_register()
{
u32 msi_val;
pci_read_config_dword(s5100pcie.s5100_pdev, 0x50, &msi_val);
mif_err("MSI Control Reg(0x50) : 0x%x\n", msi_val);
pci_read_config_dword(s5100pcie.s5100_pdev, 0x54, &msi_val);
mif_err("MSI Message Reg(0x54) : 0x%x\n", msi_val);
pci_read_config_dword(s5100pcie.s5100_pdev, 0x58, &msi_val);
mif_err("MSI MsgData Reg(0x58) : 0x%x\n", msi_val);
if (msi_val == 0x0) {
mif_err("MSI Message Reg == 0x0 - set MSI again!!!\n");
pci_restore_msi_state(s5100pcie.s5100_pdev);
mif_err("exynos_pcie_msi_init_ext is not implemented\n");
/* exynos_pcie_msi_init_ext(s5100pcie.pcie_channel_num); */
pci_read_config_dword(s5100pcie.s5100_pdev, 0x50, &msi_val);
mif_err("Recheck - MSI Control Reg : 0x%x (0x50)\n", msi_val);
pci_read_config_dword(s5100pcie.s5100_pdev, 0x54, &msi_val);
mif_err("Recheck - MSI Message Reg : 0x%x (0x54)\n", msi_val);
pci_read_config_dword(s5100pcie.s5100_pdev, 0x58, &msi_val);
mif_err("Recheck - MSI MsgData Reg : 0x%x (0x58)\n", msi_val);
}
}
static void s5100pcie_remove(struct pci_dev *pdev)
{
pr_err("S5100 PCIe Remove!!!\n");
pci_release_regions(pdev);
}
static int __init s5100_reserved_mem_setup(struct reserved_mem *rmem)
{
pr_info("reserved memory: %s = %#lx @ %pK\n",
rmem->name, (unsigned long) rmem->size, &rmem->base);
return 0;
}
RESERVEDMEM_OF_DECLARE(s5100_pcie, "modem-s5100-shmem", s5100_reserved_mem_setup);
static int __init s5100_reserved_msi_mem(struct reserved_mem *rmem)
{
pr_info("reserved memory: %s = %#lx @ %pK\n",
rmem->name, (unsigned long) rmem->size, &rmem->base);
return 0;
}
RESERVEDMEM_OF_DECLARE(s5100_pcie_msi, "s5100-msi", s5100_reserved_msi_mem);
/* For Test */
static struct pci_device_id s5100_pci_id_tbl[] =
{
{ PCI_VENDOR_ID_SAMSUNG, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, }, // SC Basic
{ }
};
MODULE_DEVICE_TABLE(pci, s5100_pci_id_tbl);
static struct pci_driver s5100_driver = {
.name = "s5100",
.id_table = s5100_pci_id_tbl,
.probe = s5100pcie_probe,
.remove = s5100pcie_remove,
};
/*
* Initialize PCIe S5100 EP driver.
*/
int s5100pcie_init(int ch_num)
{
int ret;
pr_err("Register PCIE drvier for S5100.\n");
s5100pcie.pcie_channel_num = ch_num;
ret = pci_register_driver(&s5100_driver);
/* Create PROC fs */
proc_create("driver/s5100pcie_proc", 0, NULL, &s5100pcie_proc_fops);
return 0;
}
static void __exit s5100pcie_exit(void)
{
pci_unregister_driver(&s5100_driver);
}
module_exit(s5100pcie_exit);