/* * * 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 /* module_init, module_exit */ #include #include #include #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 /* Indication if Punit has reboot sync IPC command with Atom. Default is FALSE */ static uint32_t punit_valid_version = 0; #define MAX_ACK_TIMEOUT 60000 #define MIN_ACK_TIMEOUT 0 #define PUNIT_FW_VERSION_A106 0x10006 #define PUNIT_FW_VERSION_A113 0x10103 #define ATOM_REBOOT_WAIT_TOTAL_TIME_MSEC 5000 #define FULL_SOC_REBOOT 0 #define ATOM_ONLY_REBOOT 1 #define FERR_REPORT_ENABLE (1<<10) #define PUNIT_CEFDK_APPCPU_ONLY_ENABLE (1<<4) int atom_reboot_method = ATOM_ONLY_REBOOT; static struct msr __percpu *msrs; int p_unit_reset_soc( void ) { DPRINTK("Enter, punit_reset_event=%d\n", punit_reset_event); /* In case of reset event from Punit, we just need to ACK it. */ if (punit_reset_event && punit_valid_version) { p_unit_acquire_lock(); punit_reset_event = 0; p_unit_cmd(P_UNIT_CMD_RESET_ATOM_RESET_INDICATION_ACK); p_unit_release_lock(); } else { /* notify the reboot process of appcpu is done and then wait */ mdelay(ATOM_REBOOT_WAIT_TOTAL_TIME_MSEC); if (atom_reboot_method == ATOM_ONLY_REBOOT) outb(0x1, 0xcf9); else outb(0x8, 0xcf9); } DPRINTK("Exit, punit_reset_event=%d\n", punit_reset_event); /* * This function will not return due to kernel get reboot */ while(1); return 0; } EXPORT_SYMBOL(p_unit_reset_soc); uint32_t reset_from_punit( void ) { return punit_reset_event; } EXPORT_SYMBOL(reset_from_punit); /* * brief proc file to configure P-Unit atom reset indiction ack timeout value * **/ static int p_unit_proc_control(struct file *fp, const char * buf, unsigned long count, void * data) { unsigned char local_buf[20],header[20]; int ret_val = 0; unsigned int ack_timeout; if (count > 20) { printk(KERN_ERR "Buffer Overflow\n"); return -EFAULT; } if(copy_from_user(local_buf,buf,count)) return -EFAULT; /* Ignoring last \n char */ local_buf[count-1]='\0'; ret_val = count; /* compare header with special header string*/ sscanf(local_buf, "%20s", header); if (!strcmp(header,"ack_timeout")) { sscanf(local_buf+11,"%d",&ack_timeout); if(ack_timeout <= MAX_ACK_TIMEOUT){ p_unit_acquire_lock(); if(punit_valid_version){ p_unit_cmd_wr_data(P_UNIT_CMD_RESET_ATOM_RESET_INDICATION_ACK_TIMEOUT, ack_timeout); printk(KERN_INFO "Set atom reset punit indiction ack timeout to %dms\n",ack_timeout); } p_unit_release_lock(); } else{ printk(KERN_INFO "Ack timeout value should be between 0-60000 ms\n"); return -EFAULT; } } else{ printk(KERN_INFO "Unknown operation\n"); return -EFAULT; } return ret_val; } static int reboot_proc_control(struct file *fp, const char * buf, unsigned long count, void * data) { unsigned char internal_buf[8]; int ret_val = 0; uint32_t punit_flag; if (count != 2) { printk("Invalid Parameter\n"); return -EFAULT; } if(copy_from_user(internal_buf, buf, count)) return -EFAULT; /* Ignoring last \n char */ internal_buf[count-1]='\0'; ret_val = count; p_unit_get_flag(&punit_flag); if (!(punit_flag & PUNIT_CEFDK_APPCPU_ONLY_ENABLE)) { printk(KERN_ERR"CEFDK or PUNIT does not support Atom only reboot\n"); return -EFAULT; } if (internal_buf[0] == '0') { printk("Change reboot to full soc reboot\n"); atom_reboot_method = FULL_SOC_REBOOT; p_unit_cmd(P_UNIT_CMD_WATCHDOG_DO_COLD_RESET); } else if (internal_buf[0] == '1') { printk("Change reboot to atom only reboot\n"); atom_reboot_method = ATOM_ONLY_REBOOT; p_unit_cmd(P_UNIT_CMD_WATCHDOG_DO_CPU_RESET); } else printk("Invalid Mode\n"); return ret_val; } static int __init punit_reboot_sync_init(void) { int ret = 0,fw_version = 0; u32 cpu; struct proc_dir_entry * dir; struct proc_dir_entry *dir_reboot; uint32_t punit_flag; /* We should identify if the punit abstract layer is ready to access or not*/ if(!p_unit_access_avaiable()){ ret = -EINVAL; goto _reboot_sync_exit; } p_unit_get_FwVersion(&fw_version); punit_valid_version = ((fw_version & 0xffffff)>=PUNIT_FW_VERSION_A106); DPRINTK("fw_version is %x, punit_valid_version is %d\n",fw_version,punit_valid_version); /* The Punit reboot sync function is only available when fw version is equal or higher than A1.0.6*/ if(!punit_valid_version){ ret = -EINVAL; goto _reboot_sync_exit; } /* Proc filesystem utilities.... */ if (NULL == (dir = create_proc_entry("punit_control", 0, NULL))){ printk("%s:%d ERROR ....\n",__FUNCTION__,__LINE__); ret = -EIO; goto _reboot_sync_exit; } dir->write_proc = p_unit_proc_control; if (NULL == (dir_reboot = create_proc_entry("reboot_control", 0, NULL))){ printk("%s:%d ERROR ....\n",__FUNCTION__,__LINE__); ret = -EIO; } dir_reboot->write_proc = reboot_proc_control; if(punit_valid_version){ /* ACK timeout set to 14 seconds and enable the ATOM RESET INDICATION */ p_unit_acquire_lock(); p_unit_cmd_wr_data(P_UNIT_CMD_RESET_ATOM_RESET_INDICATION_ACK_TIMEOUT, 14000); p_unit_cmd_wr_data(P_UNIT_CMD_RESET_EN_ATOM_RESET_INDICATION, 1); if (atom_reboot_method == ATOM_ONLY_REBOOT) { p_unit_cmd(P_UNIT_CMD_WATCHDOG_DO_CPU_RESET); } else if (atom_reboot_method == FULL_SOC_REBOOT) { p_unit_cmd(P_UNIT_CMD_WATCHDOG_DO_COLD_RESET); } p_unit_release_lock(); p_unit_get_flag(&punit_flag); if (!(punit_flag & PUNIT_CEFDK_APPCPU_ONLY_ENABLE)) { p_unit_cmd(P_UNIT_CMD_WATCHDOG_DO_COLD_RESET); atom_reboot_method = FULL_SOC_REBOOT; } } /* Disable #FERR reporting bit on IA32_MISC_ENABLE to prevent breaking C-state * from unexpected interrupt*/ msrs = msrs_alloc(); rdmsr_on_cpus(cpu_present_mask, MSR_IA32_MISC_ENABLE, msrs); for_each_cpu(cpu, cpu_present_mask) { struct msr *reg = per_cpu_ptr(msrs, cpu); reg->l &= ~(FERR_REPORT_ENABLE); } wrmsr_on_cpus(cpu_present_mask, MSR_IA32_MISC_ENABLE, msrs); msrs_free(msrs); msrs = NULL; _reboot_sync_exit: if (ret < 0) punit_valid_version = 0; return ret; } static void __exit punit_reboot_sync_exit(void) { /* The Punit reboot sync function is only available when fw version is higher than A1.0.6*/ if(!punit_valid_version) return; punit_valid_version = 0; punit_reset_event = 0; printk(KERN_INFO "%s:%d punit reboot sync drv has been exit\n",__FUNCTION__,__LINE__); } module_init(punit_reboot_sync_init); module_exit(punit_reboot_sync_exit); /* Driver identification */ MODULE_DESCRIPTION("Power Control Unit Reboot Sync Driver"); MODULE_LICENSE("GPL");