/* * * punit_reboot_sync.c * Description: * power control unit device reboot sync driver * * GPL LICENSE SUMMARY * * Copyright(c) 2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * The full GNU General Public License is included in this distribution * in the file called LICENSE.GPL. * * Contact Information: * Intel Corporation * 2200 Mission College Blvd. * Santa Clara, CA 97052 * */ /*------------------------------------------------------------------------------ * File Name: punit_reboot_sync.c *------------------------------------------------------------------------------ */ #include #include #include #include /* for modules */ #include /* file_operations */ #include /* copy_(to,from)_user */ #include /* module_init, module_exit */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define P_UNIT_DEBUG #ifdef P_UNIT_DEBUG /* note: prints function name for you */ # define DPRINTK(fmt, args...) printk("%-40s:%5d " fmt, __FUNCTION__,__LINE__, ## args) #else # define DPRINTK(fmt, args...) #endif #define PUNIT_DEV_NAME "p_unit" /* This is the major number assigned dynamically assigned by Linux */ /* This number is required by the mknod command. */ static int punit_major_number; static struct class *punit_class; DEFINE_MUTEX(p_unit_lock); struct iosf_host *host; /* Indication if punit driver init successfully */ uint32_t p_unit_init_done = 0; /* Indication if reset event has been received from Punit. Default is FALSE */ uint32_t punit_reset_event = 0; EXPORT_SYMBOL(punit_reset_event); /* * Data for PCI driver interface */ static DEFINE_PCI_DEVICE_TABLE(p_unit_id_tables) = { { PCI_DEVICE(0x8086, 0x08bc), }, { 0, }, /* End of list */ }; MODULE_DEVICE_TABLE(pci, p_unit_id_tables); #define IOSF_PORT_PUNIT 4 #define ADDR_ATOM_PM_CMD (0x000000DC) #define ADDR_ATOM_PM_DATA (0x000000DD) #define ADDR_PM_ATOM_CMD (0x000000DE) #define ADDR_PM_ATOM_DATA (0x000000DF) #define ATOM_PM_INTR (0x000000E0) #define PUNIT_FW_VERSION (0x00000087) #define P_UNIT_CMD_IDLE (0x0000) #define P_UNIT_CMD_ACK (0x0001) #define ATOM_PM_INTR_DIS (0x0000) #define ATOM_PM_INTR_EN (0x0001) #define PUNIT_FLAG_REG 0xD5 /*************************************** * P-UNIT Low Level Building Blocks .... * **************************************/ /* if return 0 means command no write data, otherwise yes */ static int p_unit_cmd_has_data_wr(unsigned int command) { return (command & P_UNIT_CMD_DATA_ATTACHED); } /* if return 0 means command no return data, otherwise yes */ static int p_unit_cmd_has_data_rt(unsigned int command) { return (command & P_UNIT_CMD_DATA_EXPECTED); } int p_unit_access_avaiable(void) { if(NULL != host && p_unit_init_done){ return 1; } else { DPRINTK("Punit is not avaiable to access\n"); return 0; } } void p_unit_acquire_lock(void) { mutex_lock(&p_unit_lock); } EXPORT_SYMBOL(p_unit_acquire_lock); void p_unit_release_lock(void) { mutex_unlock(&p_unit_lock); } EXPORT_SYMBOL(p_unit_release_lock); int p_unit_cmd(unsigned int command) { unsigned int cmd = command; DPRINTK("Enter cmd = 0x%08X \n", command); /* Send Command to Punit */ if (kiosf_reg_modify(host,IOSF_PORT_PUNIT, ADDR_ATOM_PM_CMD, 0xFFFF, cmd)){ return -EINVAL; } if (kiosf_reg_write32(host,IOSF_PORT_PUNIT, ATOM_PM_INTR, ATOM_PM_INTR_EN)){ return -EINVAL; } /* Complete Initiator Handshake with Punit */ do{ if (kiosf_reg_read32(host,IOSF_PORT_PUNIT, ADDR_ATOM_PM_CMD, &cmd)){ kiosf_reg_write32(host,IOSF_PORT_PUNIT, ATOM_PM_INTR, ATOM_PM_INTR_DIS); return -EINVAL; } } while (P_UNIT_CMD_ACK != cmd); if (kiosf_reg_write32(host, IOSF_PORT_PUNIT, ADDR_ATOM_PM_CMD, P_UNIT_CMD_IDLE)){ npcpu_bootcfg_ctrl_write_reg(BOOTCFG_REG_SW_INT1_CLR, BOOTCFG_REG_SW_INT1_ARM11_2_PUNIT_ISR); return -EINVAL; } kiosf_reg_write32(host,IOSF_PORT_PUNIT, ATOM_PM_INTR, ATOM_PM_INTR_DIS); DPRINTK("Exit cmd = 0x%08X \n", command); return 0; } EXPORT_SYMBOL(p_unit_cmd); int p_unit_cmd_wr_data(unsigned int command, unsigned int data) { int ret; DPRINTK("Enter cmd = 0x%08X data = 0x%08X\n", command, data); if(p_unit_cmd_has_data_wr(command) == 0){ return -EINVAL; } /* Send Data */ if(kiosf_reg_write32(host, IOSF_PORT_PUNIT, ADDR_ATOM_PM_DATA, data)){ return -EINVAL; } /* Send Command and Wait for ACK */ ret = p_unit_cmd(command); DPRINTK("Exit cmd = 0x%08X data = 0x%08X\n", command, data); return ret; } EXPORT_SYMBOL(p_unit_cmd_wr_data); int p_unit_cmd_rd_data(unsigned int command, unsigned int *data) { int ret; DPRINTK("Enter cmd = 0x%08X data_ptr = 0x%p\n", command, data); if(p_unit_cmd_has_data_rt(command) == 0){ return -EINVAL; } /* Send Command and Wait for ACK */ if(p_unit_cmd(command)){ return -EINVAL; } /* Got Returned Data */ ret = kiosf_reg_read32(host, IOSF_PORT_PUNIT, ADDR_ATOM_PM_DATA, data); DPRINTK("Exit cmd = 0x%08X data_ptr = 0x%p\n", command, data); return ret; } EXPORT_SYMBOL(p_unit_cmd_rd_data); int p_unit_get_FwVersion(unsigned int *fw_version) { p_unit_acquire_lock(); if(kiosf_reg_read32(host,IOSF_PORT_PUNIT, PUNIT_FW_VERSION, fw_version)){ p_unit_release_lock(); return -EINVAL; } p_unit_release_lock(); return 0; } EXPORT_SYMBOL(p_unit_get_FwVersion); int p_unit_get_flag(uint32_t *flag) { p_unit_acquire_lock(); if(kiosf_reg_read32(host,IOSF_PORT_PUNIT, PUNIT_FLAG_REG, flag)){ p_unit_release_lock(); return -EINVAL; } p_unit_release_lock(); return 0; } EXPORT_SYMBOL(p_unit_get_flag); /******************************************************** * Interrupt Handler Implementation... * *********************************************************/ static void p_unit_isr_cmd_ack(unsigned int command, unsigned int data) { if(p_unit_cmd_has_data_rt(command)){ kiosf_reg_write32(host, IOSF_PORT_PUNIT, ADDR_PM_ATOM_DATA, data); } kiosf_reg_write32(host, IOSF_PORT_PUNIT, ADDR_PM_ATOM_CMD, P_UNIT_CMD_ACK); } static void argv_cleanup(struct subprocess_info *info) { argv_free(info->argv); } static void atom_only_reboot(void) { int argc; char **argv = argv_split(GFP_ATOMIC, "/sbin/reboot", &argc); static char *envp[] = {"HOME=/", "PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL}; struct subprocess_info *info; info = call_usermodehelper_setup(argv[0], argv, envp, GFP_ATOMIC); if (info == NULL) { argv_free(argv); } else { call_usermodehelper_setfns(info, NULL, argv_cleanup, NULL); call_usermodehelper_exec(info, UMH_NO_WAIT); } return ; } static irqreturn_t p_unit_isr(int irq, void *dev_id) { unsigned int command = 0, data = 0; int ret = IRQ_HANDLED; DPRINTK("In the punit irq handler\n"); if (kiosf_reg_read32(host,IOSF_PORT_PUNIT, ADDR_PM_ATOM_CMD, &command)) { printk("Error in first kiosf_reg_read32 \n"); } if(p_unit_cmd_has_data_wr(command)){ kiosf_reg_read32(host,IOSF_PORT_PUNIT, ADDR_PM_ATOM_DATA, &data); } switch(command) { case P_UNIT_CMD_RESET_ATOM_RESET_INDICATION: DPRINTK("Punit Interrupted - reset indication\n"); p_unit_isr_cmd_ack(P_UNIT_CMD_RESET_ATOM_RESET_INDICATION, data); punit_reset_event = 1; /* We will start reboot process by simulating NP->APP reboot interrupt */ /* npcpu_bootcfg_ctrl_write_reg(BOOTCFG_REG_SW_INT1_CLR, BOOTCFG_REG_SW_INT1_ARM11_2_PUNIT_ISR); DPRINTK("Punit Interrupted - cleared arm interrupt\n"); npcpu_bootcfg_ctrl_write_reg(BOOTCFG_REG_SW_INT1_SET, BOOTCFG_REG_SW_INT1_ARM11_2_ATOM_REBOOT_ISR); */ atom_only_reboot(); break; case P_UNIT_CMD_BBU_EXIT_BBU_MODE: DPRINTK("Punit Interrupted - exit BBU mode indication\n"); p_unit_isr_cmd_ack(P_UNIT_CMD_BBU_EXIT_BBU_MODE, data); break; default: DPRINTK("Interrupted - unknown command 0x%04X\n", command); ret = IRQ_NONE; break; } return ret; } /************************************************ * IOCTL Implementation * ***********************************************/ static long punit_unlocked_ioctl(struct file *flip, unsigned int cmd, unsigned long arg) { int ret = 0; struct punit_cmd k_punit_cmd; void __user *argp = (void __user *)arg; if(copy_from_user(&k_punit_cmd, argp, sizeof(k_punit_cmd))) { printk(KERN_ERR "%s:%d Invalid arg for IOCTL CMD (0x%08X) has been received \n",__FUNCTION__,__LINE__,cmd); return -EINVAL; } p_unit_acquire_lock(); switch(cmd) { case P_UNIT_CMD: ret = p_unit_cmd(k_punit_cmd.command); break; case P_UNIT_CMD_WR_DATA: ret = p_unit_cmd_wr_data(k_punit_cmd.command, k_punit_cmd.data); break; case P_UNIT_CMD_RD_DATA: ret = p_unit_cmd_rd_data(k_punit_cmd.command, &(k_punit_cmd.data)); if(copy_to_user(argp, &k_punit_cmd, sizeof(k_punit_cmd))) ret = -EINVAL; break; default: printk(KERN_ERR "%s:%d Invalid IOCTL(0x%08X) has been received \n",__FUNCTION__,__LINE__,cmd); ret = -ENOSYS; break; } p_unit_release_lock(); return ret; } /* * This function is the power control unit device module init function. * @pdev: PCI device information struct * @ent: entry in p_unit_id_tables * return 0 on success else negative number. * **/ static int __devinit p_unit_access_probe(struct pci_dev *pdev, const struct pci_device_id *id) { int ret = 0; host = iosf_request(0); if (NULL == host) { printk(KERN_ERR "Failed to open iosf device\n"); return -ENODEV; } ret = pci_enable_device(pdev); if(ret) goto err_iosf_read; ret = request_irq(pdev->irq, p_unit_isr, IRQF_TRIGGER_RISING| IRQF_ONESHOT| IRQF_DISABLED, "punit_int", NULL ); if (ret) { printk(KERN_ERR "PUNIT: Unable to allocate pUnit IRQ\n"); goto err_punit_cmd; } return 0; err_punit_cmd: free_irq(pdev->irq, NULL); err_iosf_read: iosf_release(host); return ret; } /** * p_unit_remove - Device Removal Routine * @pdev: PCI device information struct * This function is the power control unit device module exit function. * */ static void __devexit p_unit_access_remove(struct pci_dev *pdev) { iosf_release(host); free_irq(pdev->irq, NULL); printk(KERN_INFO "%s:%d device has been unregistered\n",__FUNCTION__,__LINE__); } static struct pci_driver p_unit_driver = { .name = "p_unit_access_drv", .probe = p_unit_access_probe, .remove = __devexit_p(p_unit_access_remove), .id_table = p_unit_id_tables, }; struct file_operations p_unit_ops = { .owner = THIS_MODULE, .unlocked_ioctl = punit_unlocked_ioctl, .open = NULL, .release = NULL, }; static int __init punit_access_init(void) { int ret = 0; ret = pci_register_driver(&p_unit_driver); if(ret){ printk(KERN_ERR "Punit access pci driver register failure\n"); return ret; } /* Register the device with the operating system */ if((punit_major_number = register_chrdev(0, PUNIT_DEV_NAME, &p_unit_ops)) < 0) { printk(KERN_ERR "%s:%4i: Punit access device registration failed - code %d\n", __FILE__, __LINE__, punit_major_number); return punit_major_number; } /* node create automatically */ punit_class = class_create(THIS_MODULE, PUNIT_DEV_NAME); device_create(punit_class, NULL, MKDEV(punit_major_number, 0), NULL, PUNIT_DEV_NAME); p_unit_init_done = 1; return 0; } static void __exit punit_access_exit(void) { unregister_chrdev(punit_major_number, PUNIT_DEV_NAME); device_destroy(punit_class, MKDEV(punit_major_number, 0)); class_destroy(punit_class); pci_unregister_driver(&p_unit_driver); p_unit_init_done = 0; } module_init(punit_access_init); module_exit(punit_access_exit); /* Driver identification */ MODULE_DESCRIPTION("Power Control Unit Access Driver"); MODULE_LICENSE("GPL");