treewide: Import Samsung changes from T725XXU2DUD1

Change-Id: I5c31dc4a8006a967910963fb9e7d1a0ab4ab9731
This commit is contained in:
Bruno Martins
2021-05-15 14:32:00 +01:00
committed by LuK1337
parent 7e84375adb
commit dbcc8fefd9
1057 changed files with 297672 additions and 13164 deletions

View File

@@ -982,6 +982,14 @@ ifeq ($(CONFIG_STRIP_ASM_SYMS),y)
LDFLAGS_vmlinux += $(call ld-option, -X,)
endif
ifneq ($(SEC_BUILD_CONF_USE_FINGERPRINT_TZ),false)
ifeq ($(CONFIG_SENSORS_FINGERPRINT),y)
ifneq ($(CONFIG_SEC_FACTORY),y)
export KBUILD_FP_SENSOR_CFLAGS := -DENABLE_SENSORS_FPRINT_SECURE
endif
endif
endif
# Default kernel image to build when no specific target is given.
# KBUILD_IMAGE may be overruled on the command line or
# set in the environment

View File

@@ -1270,6 +1270,8 @@ config KEYS_COMPAT
endmenu
source "arch/arm64/Kconfig.sec"
menu "Power management options"
source "kernel/power/Kconfig"

View File

@@ -65,6 +65,22 @@ config DEBUG_ALIGN_RODATA
config ARM64_STRICT_BREAK_BEFORE_MAKE
bool "Enforce strict break-before-make on page table updates "
comment "PowerManagement Feature"
menuconfig SEC_PM
bool "Samsung TN PowerManagement Feature"
default y
help
Samsung TN PowerManagement Feature.
if SEC_PM
config SEC_PM_DEBUG
bool "Samsung TN PowerManagement Debug Feature"
default n
help
Samsung TN PowerManagement Debug Feature.
endif
source "drivers/hwtracing/coresight/Kconfig"
endmenu

49
arch/arm64/Kconfig.sec Normal file
View File

@@ -0,0 +1,49 @@
config SEC_GTS4LV_PROJECT
depends on ARCH_SDM670
default n
bool "Samsung GTS4LV Project"
help
Support for Samsung GTS4LV Project
config SEC_GTS4LV_MEA_PROJECT
depends on SEC_GTS4LV_PROJECT
default n
bool "Samsung GTS4LV MEA Project"
help
Support for Samsung GTS4LV MEA Project
config SEC_GTS4LV_EUR_PROJECT
depends on SEC_GTS4LV_PROJECT
default n
bool "Samsung GTS4LV EUR OPEN Project"
help
Support for Samsung GTS4LV EUR OPEN Project
config SEC_GTS4LVWIFI_EUR_PROJECT
depends on SEC_GTS4LV_PROJECT
default n
bool "Samsung GTS4LVWIFI EUR OPEN Project"
help
Support for Samsung GTS4LVWIFI EUR OPEN Project
config SEC_GTS4LVWIFI_EUR_LDU_PROJECT
depends on SEC_GTS4LV_PROJECT
default n
bool "Samsung GTS4LVWIFI EUR LDU Project"
help
Support for Samsung GTS4LVWIFI EUR LDU Project
config SEC_GTS4LV_KOR_PROJECT
depends on SEC_GTS4LV_PROJECT
default n
bool "Samsung GTS4LV KOR OPEN Project"
help
Support for Samsung GTS4LV KOR OPEN Project
config SEC_GTS4LV_USA_PROJECT
depends on SEC_GTS4LV_PROJECT
default n
bool "Samsung GTS4LV USA Project"
help
Support for Samsung GTS4LV USA Project

View File

@@ -0,0 +1,194 @@
/*
* arch/arm64/include/asm/sec_debug.h
*
* COPYRIGHT(C) 2006-2016 Samsung Electronics Co., Ltd. All Right Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef SEC_DEBUG_ARM64_H
#define SEC_DEBUG_ARM64_H
#if defined(CONFIG_ARM64) && defined(CONFIG_SEC_DEBUG)
struct sec_debug_mmu_reg_t {
uint64_t TTBR0_EL1;
uint64_t TTBR1_EL1;
uint64_t TCR_EL1;
uint64_t MAIR_EL1;
uint64_t ATCR_EL1;
uint64_t AMAIR_EL1;
uint64_t HSTR_EL2;
uint64_t HACR_EL2;
uint64_t TTBR0_EL2;
uint64_t VTTBR_EL2;
uint64_t TCR_EL2;
uint64_t VTCR_EL2;
uint64_t MAIR_EL2;
uint64_t ATCR_EL2;
uint64_t TTBR0_EL3;
uint64_t MAIR_EL3;
uint64_t ATCR_EL3;
};
/* ARM CORE regs mapping structure */
struct sec_debug_core_t {
/* COMMON */
uint64_t x0;
uint64_t x1;
uint64_t x2;
uint64_t x3;
uint64_t x4;
uint64_t x5;
uint64_t x6;
uint64_t x7;
uint64_t x8;
uint64_t x9;
uint64_t x10;
uint64_t x11;
uint64_t x12;
uint64_t x13;
uint64_t x14;
uint64_t x15;
uint64_t x16;
uint64_t x17;
uint64_t x18;
uint64_t x19;
uint64_t x20;
uint64_t x21;
uint64_t x22;
uint64_t x23;
uint64_t x24;
uint64_t x25;
uint64_t x26;
uint64_t x27;
uint64_t x28;
uint64_t x29; /* sp */
uint64_t x30; /* lr */
uint64_t pc; /* pc */
uint64_t cpsr; /* cpsr */
/* EL0 */
uint64_t sp_el0;
/* EL1 */
uint64_t sp_el1;
uint64_t elr_el1;
uint64_t spsr_el1;
/* EL2 */
uint64_t sp_el2;
uint64_t elr_el2;
uint64_t spsr_el2;
/* EL3 */
/* uint64_t sp_el3; */
/* uint64_t elr_el3; */
/* uint64_t spsr_el3; */
};
#define READ_SPECIAL_REG(x) ({ \
uint64_t val; \
asm volatile ("mrs %0, " # x : "=r"(val)); \
val; \
})
static inline void sec_debug_save_mmu_reg(struct sec_debug_mmu_reg_t *mmu_reg)
{
uint64_t pstate, which_el;
pstate = READ_SPECIAL_REG(CurrentEl);
which_el = pstate & PSR_MODE_MASK;
/* pr_emerg("%s: sec_debug EL mode=%d\n", __func__,which_el); */
mmu_reg->TTBR0_EL1 = READ_SPECIAL_REG(TTBR0_EL1);
mmu_reg->TTBR1_EL1 = READ_SPECIAL_REG(TTBR1_EL1);
mmu_reg->TCR_EL1 = READ_SPECIAL_REG(TCR_EL1);
mmu_reg->MAIR_EL1 = READ_SPECIAL_REG(MAIR_EL1);
mmu_reg->AMAIR_EL1 = READ_SPECIAL_REG(AMAIR_EL1);
}
static inline void sec_debug_save_core_reg(struct sec_debug_core_t *core_reg)
{
uint64_t pstate,which_el;
pstate = READ_SPECIAL_REG(CurrentEl);
which_el = pstate & PSR_MODE_MASK;
/* pr_emerg("%s: sec_debug EL mode=%d\n", __func__,which_el); */
asm volatile (
"str x0, [%0,#0]\n\t" /* x0 is pushed first to core_reg */
"mov x0, %0\n\t"
"add x0, x0, 0x8\n\t"
"stp x1, x2, [x0], #0x10\n\t"
"stp x3, x4, [x0], #0x10\n\t"
"stp x5, x6, [x0], #0x10\n\t"
"stp x7, x8, [x0], #0x10\n\t"
"stp x9, x10, [x0], #0x10\n\t"
"stp x11, x12, [x0], #0x10\n\t"
"stp x13, x14, [x0], #0x10\n\t"
"stp x15, x16, [x0], #0x10\n\t"
"stp x17, x18, [x0], #0x10\n\t"
"stp x19, x20, [x0], #0x10\n\t"
"stp x21, x22, [x0], #0x10\n\t"
"stp x23, x24, [x0], #0x10\n\t"
"stp x25, x26, [x0], #0x10\n\t"
"stp x27, x28, [x0], #0x10\n\t"
"stp x29, x30, [x0], #0x10\n\t"
/* pc */
"adr x1, .\n\t"
/* pstate */
"mrs x15, NZCV\n\t"
"bic x15, x15, #0xFFFFFFFF0FFFFFFF\n\t"
"mrs x9, DAIF\n\t"
"bic x9, x9, #0xFFFFFFFFFFFFFC3F\n\t"
"orr x15, x15, x9\n\t"
"mrs x10, CurrentEL\n\t"
"bic x10, x10, #0xFFFFFFFFFFFFFFF3\n\t"
"orr x15, x15, x10\n\t"
"mrs x11, SPSel\n\t"
"bic x11, x11, #0xFFFFFFFFFFFFFFFE\n\t"
"orr x15, x15, x11\n\t"
/* store pc & pstate */
"stp x1, x15, [x0], #0x10\n\t"
: /* output */
: "r"(core_reg) /* input */
: "%x0", "%x1" /* clobbered registers */
);
core_reg->sp_el0 = READ_SPECIAL_REG(sp_el0);
if(which_el >= PSR_MODE_EL2t){
core_reg->sp_el0 = READ_SPECIAL_REG(sp_el1);
core_reg->elr_el1 = READ_SPECIAL_REG(elr_el1);
core_reg->spsr_el1 = READ_SPECIAL_REG(spsr_el1);
core_reg->sp_el2 = READ_SPECIAL_REG(sp_el2);
core_reg->elr_el2 = READ_SPECIAL_REG(elr_el2);
core_reg->spsr_el2 = READ_SPECIAL_REG(spsr_el2);
}
}
#endif /* defined(CONFIG_ARM64) && defined(CONFIG_SEC_DEBUG) */
#endif /* SEC_DEBUG_ARM64_H */

View File

@@ -21,6 +21,6 @@
#include <linux/types.h>
#define COMMAND_LINE_SIZE 2048
#define COMMAND_LINE_SIZE 4096
#endif

View File

@@ -59,6 +59,8 @@
#define CREATE_TRACE_POINTS
#include <trace/events/ipi.h>
#include <linux/sec_debug.h>
DEFINE_PER_CPU_READ_MOSTLY(int, cpu_number);
EXPORT_PER_CPU_SYMBOL(cpu_number);
@@ -853,6 +855,7 @@ static void ipi_cpu_stop(unsigned int cpu, struct pt_regs *regs)
pr_crit("CPU%u: stopping\n", cpu);
show_regs(regs);
dump_stack();
sec_debug_save_context();
dump_stack_minidump(regs->sp);
raw_spin_unlock(&stop_lock);
}

View File

@@ -46,6 +46,10 @@
#include <asm/sysreg.h>
#include <trace/events/exception.h>
#include <linux/sec_debug.h>
#include <linux/sec_debug_summary.h>
#include <linux/sec_debug_user_reset.h>
static const char *handler[]= {
"Synchronous Abort",
"IRQ",
@@ -256,6 +260,17 @@ static int __die(const char *str, int err, struct pt_regs *regs)
end_of_stack(tsk));
if (!user_mode(regs)) {
#ifdef CONFIG_SEC_DEBUG
if (THREAD_SIZE + (unsigned long)task_stack_page(tsk) - regs->sp
> THREAD_SIZE) {
dump_mem(KERN_EMERG, "Stack: ", regs->sp,
THREAD_SIZE / 4 + regs->sp);
} else {
dump_mem(KERN_EMERG, "Stack: ", regs->sp, THREAD_SIZE
+ (unsigned long)task_stack_page(tsk));
}
#endif
dump_backtrace(regs, tsk);
dump_instr(KERN_EMERG, regs);
}
@@ -273,6 +288,7 @@ static unsigned long oops_begin(void)
unsigned long flags;
oops_enter();
secdbg_sched_msg("!!die!!");
/* racy, but better than risking deadlock. */
raw_local_irq_save(flags);
@@ -327,6 +343,8 @@ void die(const char *str, struct pt_regs *regs, int err)
if (bug_type != BUG_TRAP_TYPE_NONE && !strlen(str))
str = "Oops - BUG";
sec_debug_save_die_info(str, regs);
ret = __die(str, err, regs);
oops_end(flags, regs, ret);
@@ -801,6 +819,9 @@ asmlinkage void bad_mode(struct pt_regs *regs, int reason, unsigned int esr)
{
console_verbose();
sec_debug_save_badmode_info(reason, handler[reason],
esr, esr_get_class_string(esr));
pr_crit("Bad mode in %s handler detected on CPU%d, code 0x%08x -- %s\n",
handler[reason], smp_processor_id(), esr,
esr_get_class_string(esr));

View File

@@ -43,6 +43,8 @@
#include <soc/qcom/scm.h>
#include <trace/events/exception.h>
#include <linux/sec_debug_user_reset.h>
struct fault_info {
int (*fn)(unsigned long addr, unsigned int esr,
struct pt_regs *regs);
@@ -102,13 +104,19 @@ void show_pte(unsigned long addr)
} else {
pr_alert("[%016lx] address between user and kernel address ranges\n",
addr);
sec_debug_store_pte((unsigned long)addr, 1);
return;
}
pr_alert("pgd = %p\n", mm->pgd);
sec_debug_store_pte((unsigned long)mm->pgd, 0);
pgd = pgd_offset(mm, addr);
pr_alert("[%016lx] *pgd=%016llx", addr, pgd_val(*pgd));
sec_debug_store_pte((unsigned long)addr, 1);
sec_debug_store_pte((unsigned long)pgd_val(*pgd), 2);
do {
pud_t *pud;
pmd_t *pmd;
@@ -119,16 +127,20 @@ void show_pte(unsigned long addr)
pud = pud_offset(pgd, addr);
pr_cont(", *pud=%016llx", pud_val(*pud));
sec_debug_store_pte((unsigned long)pud_val(*pud), 3);
if (pud_none(*pud) || pud_bad(*pud))
break;
pmd = pmd_offset(pud, addr);
pr_cont(", *pmd=%016llx", pmd_val(*pmd));
sec_debug_store_pte((unsigned long)pmd_val(*pmd), 4);
if (pmd_none(*pmd) || pmd_bad(*pmd))
break;
pte = pte_offset_map(pmd, addr);
pr_cont(", *pte=%016llx", pte_val(*pte));
sec_debug_store_pte((unsigned long)pte_val(*pte), 5);
pte_unmap(pte);
} while(0);
@@ -203,6 +215,7 @@ static void __do_kernel_fault(unsigned long addr, unsigned int esr,
if (!is_el1_instruction_abort(esr) && fixup_exception(regs))
return;
sec_debug_store_extc_idx(false);
/*
* No handler, we'll have to terminate things with extreme prejudice.
*/
@@ -639,6 +652,8 @@ asmlinkage void __exception do_mem_abort(unsigned long addr, unsigned int esr,
const struct fault_info *inf = esr_to_fault_info(esr);
struct siginfo info;
sec_debug_save_fault_info(esr, inf->name, addr, 0UL);
if (!inf->fn(addr, esr, regs))
return;
@@ -696,6 +711,9 @@ asmlinkage void __exception do_sp_pc_abort(unsigned long addr,
esr_get_class_string(esr), (void *)regs->pc,
(void *)regs->sp);
sec_debug_save_fault_info(esr, esr_get_class_string(esr),
(unsigned long)regs->pc, (unsigned long)regs->sp);
info.si_signo = SIGBUS;
info.si_errno = 0;
info.si_code = BUS_ADRALN;
@@ -743,6 +761,8 @@ asmlinkage int __exception do_debug_exception(unsigned long addr_if_watchpoint,
struct siginfo info;
int rv;
sec_debug_save_fault_info(esr, inf->name, addr_if_watchpoint, 0UL);
/*
* Tell lockdep we disabled irqs in entry.S. Do nothing if they were
* already disabled to preserve the last enabled/disabled addresses.

View File

@@ -379,7 +379,7 @@ EXPORT_SYMBOL(blk_put_queue);
* If not, only ELVPRIV requests are drained. The caller is responsible
* for ensuring that no new requests which need to be drained are queued.
*/
static void __blk_drain_queue(struct request_queue *q, bool drain_all)
void __blk_drain_queue(struct request_queue *q, bool drain_all)
__releases(q->queue_lock)
__acquires(q->queue_lock)
{

View File

@@ -24,6 +24,14 @@ source "drivers/nvme/Kconfig"
source "drivers/misc/Kconfig"
source "drivers/staging/samsung/sec_notifier/Kconfig"
source "drivers/muic/Kconfig"
source "drivers/muic/universal/Kconfig"
source "drivers/ccic/Kconfig"
source "drivers/ide/Kconfig"
source "drivers/scsi/Kconfig"
@@ -106,6 +114,8 @@ source "drivers/memstick/Kconfig"
source "drivers/leds/Kconfig"
source "drivers/switch/Kconfig"
source "drivers/accessibility/Kconfig"
source "drivers/infiniband/Kconfig"
@@ -212,4 +222,20 @@ source "drivers/sensors/Kconfig"
source "drivers/tee/Kconfig"
source "drivers/adsp_factory/Kconfig"
source "drivers/fingerprint/Kconfig"
source "drivers/redriver/Kconfig"
source "drivers/debug/Kconfig"
source "drivers/gud/Kconfig"
source "drivers/battery_v2/Kconfig"
source "drivers/motor/Kconfig"
source "drivers/security/samsung/tzic/Kconfig"
endmenu

View File

@@ -130,6 +130,7 @@ obj-$(CONFIG_CPU_FREQ) += cpufreq/
obj-$(CONFIG_CPU_IDLE) += cpuidle/
obj-y += mmc/
obj-$(CONFIG_MEMSTICK) += memstick/
obj-$(CONFIG_SWITCH) += switch/
obj-$(CONFIG_NEW_LEDS) += leds/
obj-$(CONFIG_INFINIBAND) += infiniband/
obj-$(CONFIG_SGI_SN) += sn/
@@ -161,6 +162,9 @@ obj-$(CONFIG_HYPERV) += hv/
obj-$(CONFIG_PM_DEVFREQ) += devfreq/
obj-$(CONFIG_EXTCON) += extcon/
obj-$(CONFIG_VBUS_NOTIFIER) += staging/samsung/sec_notifier/
obj-$(CONFIG_USE_MUIC) += muic/
obj-y += ccic/
obj-$(CONFIG_MEMORY) += memory/
obj-$(CONFIG_IIO) += iio/
obj-$(CONFIG_VME_BUS) += vme/
@@ -181,3 +185,20 @@ obj-$(CONFIG_ESOC) += esoc/
obj-$(CONFIG_FPGA) += fpga/
obj-$(CONFIG_SENSORS_SSC) += sensors/
obj-$(CONFIG_TEE) += tee/
obj-$(CONFIG_ADSP_FACTORY) += adsp_factory/
obj-y += debug/
obj-$(CONFIG_BATTERY_SAMSUNG) += battery_v2/
# COMBO REDRIVER
obj-$(CONFIG_COMBO_REDRIVER) += redriver/
# FINGERPRINT
obj-$(CONFIG_SENSORS_FINGERPRINT) += fingerprint/
# mobicore
obj-$(CONFIG_TRUSTONIC_TEE) += gud/
# Motor
obj-y += motor/
#TZIC
obj-y += security/samsung/tzic/

View File

@@ -0,0 +1,81 @@
#
# factory sensor drivers configuration
#
config ADSP_FACTORY
tristate "MSM ADSP factory driver"
default n
help
This driver communicate with SSC DAEMON.
register each sensor device.
send selftest request using netlink.
receive test result using netlink.
config LSM6DSM_FACTORY
tristate "factory test for SSC - LSM6DSM"
default n
help
lsm6dsM factory driver.
provide sysfs for factory test.
request selftest to adsp_factory.
receive test result from adsp_factory.
config ICM42605_FACTORY
tristate "factory test for SSC - ICM42605"
default n
help
icm42605 factory driver.
provide sysfs for factory test.
request selftest to adsp_factory.
receive test result from adsp_factory.
config AK09918_FACTORY
tristate "factory test for SSC - ak09918"
default n
help
ak09918 factory driver.
provide sysfs for factory test.
request selftest to adsp_factory.
receive test result from adsp_factory.
config LPS22HH_FACTORY
tristate "factory test for SSC - lps22hh"
default n
help
lps22hh factory driver.
provide sysfs for factory test.
request selftest to adsp_factory.
receive test result from adsp_factory.
config VCNL36863_FACTORY
tristate "factory test for SSC - vcnl36863"
default n
help
vcnl36863 factory driver.
provide sysfs for factory test.
request selftest to adsp_factory.
receive test result from adsp_factory.
config VEML3328_FACTORY
tristate "factory test for SSC - veml3328"
default n
help
veml3328 factory driver.
provide sysfs for factory test.
request selftest to adsp_factory.
receive test result from adsp_factory.
config VCNL36658_FACTORY
tristate "factory test for SSC - vcnl36658"
default n
help
vcnl36658 factory driver.
provide sysfs for factory test.
request selftest to adsp_factory.
receive test result from adsp_factory.
config SUPPORT_PROX_AUTO_CAL
tristate "Support auto cal function for proximity sensor"
default n
depends on ADSP_FACTORY
help
Support the auto cal function.

View File

@@ -0,0 +1,9 @@
obj-$(CONFIG_ADSP_FACTORY) += adsp_factory.o ssc_core.o
obj-$(CONFIG_LSM6DSM_FACTORY) += lsm6dsl_accel.o lsm6dsl_gyro.o
obj-$(CONFIG_ICM42605_FACTORY) += icm42605_accel.o icm42605_gyro.o
obj-$(CONFIG_AK09918_FACTORY) += ak09918_mag.o
obj-$(CONFIG_VCNL36863_FACTORY) += vcnl36863_light.o vcnl36863_prox.o
obj-$(CONFIG_VCNL36658_FACTORY) += vcnl36658_light.o vcnl36658_prox.o
obj-$(CONFIG_VEML3328_FACTORY) += veml3328_light.o
obj-$(CONFIG_LPS22HH_FACTORY) += lps22hh_pressure.o

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
*/
#ifndef __ADSP_SENSOR_H__
#define __ADSP_SENSOR_H__
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/fcntl.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
//#include <linux/sensors.h>
#include <linux/adsp/adsp_ft_common.h>
#define TIMEOUT_CNT 200
/* Main struct containing all the data */
struct adsp_data {
struct device *adsp;
struct device *sensor_device[MSG_SENSOR_MAX];
struct device_attribute **sensor_attr[MSG_SENSOR_MAX];
struct device *mobeam_device;
struct sock *adsp_skt;
int32_t *msg_buf[MSG_SENSOR_MAX];
unsigned int ready_flag[MSG_TYPE_MAX];
bool sysfs_created[MSG_SENSOR_MAX];
struct mutex prox_factory_mutex;
struct mutex light_factory_mutex;
struct mutex accel_factory_mutex;
struct mutex remove_sysfs_mutex;
};
#ifdef CONFIG_SUPPORT_MOBEAM
void adsp_mobeam_register(struct device_attribute *attributes[]);
void adsp_mobeam_unregister(struct device_attribute *attributes[]);
#endif
#ifdef CONFIG_SEC_FACTORY
int get_mag_raw_data(int32_t *raw_data);
#endif
int get_accel_raw_data(int32_t *raw_data);
int get_prox_raw_data(int *raw_data, int *offset);
int adsp_get_sensor_data(int sensor_type);
int adsp_factory_register(unsigned int type,
struct device_attribute *attributes[]);
int adsp_factory_unregister(unsigned int type);
int adsp_unicast(void *param, int param_size, u16 sensor_type,
u32 portid, u16 msg_type);
int sensors_register(struct device **dev, void *drvdata,
struct device_attribute *attributes[], char *name);
void sensors_unregister(struct device *dev,
struct device_attribute *attributes[]);
void hidden_hole_init_work(void);
void accel_factory_init_work(void);
#endif

View File

@@ -0,0 +1,344 @@
/*
* Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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/platform_device.h>
#include <net/sock.h>
#include <net/netlink.h>
#include <linux/skbuff.h>
#include <linux/netlink.h>
#include <linux/mutex.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/sec_class.h>
#include "adsp.h"
static u8 msg_size[MSG_SENSOR_MAX] = {
MSG_ACCEL_MAX,
MSG_GYRO_MAX,
MSG_MAG_MAX,
MSG_PRESSURE_MAX,
MSG_LIGHT_MAX,
MSG_PROX_MAX,
MSG_TYPE_SIZE_ZERO, //hole
MSG_MOBEAM_MAX,
MSG_TYPE_SIZE_ZERO, //physical
MSG_GYRO_TEMP_MAX,
MSG_PRESSURE_TEMP_MAX,
MSG_TYPE_SIZE_ZERO,
MSG_TYPE_SIZE_ZERO,
MSG_TYPE_SIZE_ZERO,
};
/* The netlink socket */
struct adsp_data *data;
DEFINE_MUTEX(factory_mutex);
/* Function used to send message to the user space */
int adsp_unicast(void *param, int param_size, u16 sensor_type,
u32 portid, u16 msg_type)
{
struct sk_buff *skb;
struct nlmsghdr *nlh;
void *msg;
int ret = -1;
u16 nlmsg_type = (sensor_type << 8) | msg_type;
data->ready_flag[msg_type] &= ~(1 << sensor_type);
skb = nlmsg_new(param_size, GFP_KERNEL);
if (!skb) {
pr_err("[FACTORY] %s - nlmsg_new fail\n", __func__);
return -ENOMEM;
}
nlh = nlmsg_put(skb, portid, 0, nlmsg_type, param_size, 0);
if (nlh == NULL) {
pr_err("[FACTORY] %s - nlmsg_put fail\n", __func__);
nlmsg_free(skb);
return -EMSGSIZE;
}
msg = nlmsg_data(nlh);
memcpy(msg, param, param_size);
NETLINK_CB(skb).dst_group = 0;
ret = nlmsg_unicast(data->adsp_skt, skb, PID);
if (ret != 0)
pr_err("[FACTORY] %s - ret = %d\n", __func__, ret);
return ret;
}
int adsp_factory_register(unsigned int type,
struct device_attribute *attributes[])
{
int ret = 0;
char *dev_name;
switch (type) {
case MSG_ACCEL:
dev_name = "accelerometer_sensor";
break;
case MSG_GYRO:
dev_name = "gyro_sensor";
break;
case MSG_MAG:
dev_name = "magnetic_sensor";
break;
case MSG_PRESSURE:
dev_name = "barometer_sensor";
break;
case MSG_LIGHT:
dev_name = "light_sensor";
break;
case MSG_PROX:
dev_name = "proximity_sensor";
break;
case MSG_SSC_CORE:
dev_name = "ssc_core";
break;
case MSG_HH_HOLE:
dev_name = "hidden_hole";
break;
default:
dev_name = "unknown_sensor";
break;
}
data->sensor_attr[type] = attributes;
ret = sensors_register(&data->sensor_device[type], data,
data->sensor_attr[type], dev_name);
data->sysfs_created[type] = true;
pr_info("[FACTORY] %s - type:%u ptr:%pK\n",
__func__, type, data->sensor_device[type]);
return ret;
}
int adsp_factory_unregister(unsigned int type)
{
pr_info("[FACTORY] %s - type:%u ptr:%pK\n",
__func__, type, data->sensor_device[type]);
if (data->sysfs_created[type]) {
sensors_unregister(data->sensor_device[type],
data->sensor_attr[type]);
data->sysfs_created[type] = false;
} else {
pr_info("[FACTORY] %s: skip type %u\n", __func__, type);
}
return 0;
}
int get_prox_raw_data(int *raw_data, int *offset)
{
uint8_t cnt = 0;
mutex_lock(&data->prox_factory_mutex);
adsp_unicast(NULL, 0, MSG_PROX, 0, MSG_TYPE_GET_RAW_DATA);
while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_PROX) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_PROX);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
mutex_unlock(&data->prox_factory_mutex);
return -1;
}
*raw_data = data->msg_buf[MSG_PROX][0];
*offset = data->msg_buf[MSG_PROX][1];
mutex_unlock(&data->prox_factory_mutex);
return 0;
}
int get_accel_raw_data(int32_t *raw_data)
{
uint8_t cnt = 0;
adsp_unicast(NULL, 0, MSG_ACCEL, 0, MSG_TYPE_GET_RAW_DATA);
while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_ACCEL) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_ACCEL);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
return -1;
}
memcpy(raw_data, &data->msg_buf[MSG_ACCEL][0], sizeof(int32_t) * 3);
return 0;
}
#ifdef CONFIG_SEC_FACTORY
int get_mag_raw_data(int32_t *raw_data)
{
uint8_t cnt = 0;
adsp_unicast(NULL, 0, MSG_MAG, 0, MSG_TYPE_GET_RAW_DATA);
while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_MAG) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_MAG);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
return -1;
}
memcpy(raw_data, &data->msg_buf[MSG_MAG][0], sizeof(int32_t) * 3);
return 0;
}
#endif
#ifdef CONFIG_SUPPORT_MOBEAM
void adsp_mobeam_register(struct device_attribute *attributes[])
{
int i;
data->mobeam_device = sec_device_create(0, data, "sec_barcode_emul");
for (i = 0; attributes[i] != NULL; i++) {
if (device_create_file(data->mobeam_device, attributes[i]) < 0)
pr_err("%s fail to create %d", __func__, i);
}
}
void adsp_mobeam_unregister(struct device_attribute *attributes[])
{
int i;
for (i = 0; attributes[i] != NULL; i++)
device_remove_file(data->mobeam_device, attributes[i]);
}
#endif
static int process_received_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
{
u16 sensor_type = nlh->nlmsg_type >> 8;
u16 msg_type = nlh->nlmsg_type & 0xff;
/* check the boundary to prevent memory attack */
if (msg_type >= MSG_TYPE_MAX || sensor_type >= MSG_SENSOR_MAX ||
nlh->nlmsg_len - (int32_t)sizeof(struct nlmsghdr) >
sizeof(int32_t) * msg_size[sensor_type]) {
pr_err("[FACTORY] %d, %d, %d\n", msg_type, sensor_type, nlh->nlmsg_len);
return 0;
}
if (sensor_type == MSG_FACTORY_INIT_CMD) {
accel_factory_init_work();
#if defined(CONFIG_SUPPORT_HIDDEN_HOLE)
hidden_hole_init_work();
#endif
return 0;
}
memcpy(data->msg_buf[sensor_type],
(int32_t *)NLMSG_DATA(nlh),
nlh->nlmsg_len - (int32_t)sizeof(struct nlmsghdr));
data->ready_flag[msg_type] |= 1 << sensor_type;
return 0;
}
static void factory_receive_skb(struct sk_buff *skb)
{
struct nlmsghdr *nlh;
int len;
int err;
nlh = (struct nlmsghdr *)skb->data;
len = skb->len;
while (NLMSG_OK(nlh, len)) {
err = process_received_msg(skb, nlh);
/* if err or if this message says it wants a response */
if (err || (nlh->nlmsg_flags & NLM_F_ACK))
netlink_ack(skb, nlh, err);
nlh = NLMSG_NEXT(nlh, len);
}
}
/* Receive messages from netlink socket. */
static void factory_test_result_receive(struct sk_buff *skb)
{
mutex_lock(&factory_mutex);
factory_receive_skb(skb);
mutex_unlock(&factory_mutex);
}
struct netlink_kernel_cfg netlink_cfg = {
.input = factory_test_result_receive,
};
static int __init factory_adsp_init(void)
{
int i;
pr_info("[FACTORY] %s\n", __func__);
data = kzalloc(sizeof(*data), GFP_KERNEL);
for (i = 0; i < MSG_SENSOR_MAX; i++) {
if (msg_size[i] > 0)
data->msg_buf[i] = kzalloc(sizeof(int32_t) * msg_size[i],
GFP_KERNEL);
}
data->adsp_skt = netlink_kernel_create(&init_net,
NETLINK_ADSP_FAC, &netlink_cfg);
for (i = 0; i < MSG_SENSOR_MAX; i++)
data->sysfs_created[i] = false;
for (i = 0; i < MSG_TYPE_MAX; i++)
data->ready_flag[i] = 0;
mutex_init(&data->accel_factory_mutex);
mutex_init(&data->prox_factory_mutex);
mutex_init(&data->light_factory_mutex);
mutex_init(&data->remove_sysfs_mutex);
pr_info("[FACTORY] %s: Timer Init\n", __func__);
return 0;
}
static void __exit factory_adsp_exit(void)
{
int i;
mutex_destroy(&data->accel_factory_mutex);
mutex_destroy(&data->prox_factory_mutex);
mutex_destroy(&data->light_factory_mutex);
mutex_destroy(&data->remove_sysfs_mutex);
for (i = 0; i < MSG_SENSOR_MAX; i++)
kfree(data->msg_buf[i]);
pr_info("[FACTORY] %s\n", __func__);
}
module_init(factory_adsp_init);
module_exit(factory_adsp_exit);
MODULE_DESCRIPTION("Support for factory test sensors (adsp)");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,253 @@
/*
* Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 "adsp.h"
#define VENDOR "AKM"
#define CHIP_ID "AK09918"
#define MAG_ST_TRY_CNT 3
static ssize_t mag_vendor_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR);
}
static ssize_t mag_name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID);
}
static ssize_t mag_check_cntl(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "OK\n");
}
static ssize_t mag_check_registers(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE,
"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}
static ssize_t mag_get_asa(struct device *dev,
struct device_attribute *attr, char *buf)
{
/* Do not have Fuserom */
return snprintf(buf, PAGE_SIZE, "%u,%u,%u\n", 128, 128, 128);
}
static ssize_t mag_get_status(struct device *dev,
struct device_attribute *attr, char *buf)
{
/* Do not have Fuserom */
return snprintf(buf, PAGE_SIZE, "%s\n", "OK");
}
static ssize_t mag_raw_data_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
adsp_unicast(NULL, 0, MSG_MAG, 0, MSG_TYPE_GET_RAW_DATA);
while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_MAG) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_MAG);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
return snprintf(buf, PAGE_SIZE, "0,0,0\n");
}
return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n",
data->msg_buf[MSG_MAG][0],
data->msg_buf[MSG_MAG][1],
data->msg_buf[MSG_MAG][2]);
}
static ssize_t mag_raw_data_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
adsp_unicast(NULL, 0, MSG_MAG_CAL, 0, MSG_TYPE_FACTORY_ENABLE);
msleep(20);
adsp_unicast(NULL, 0, MSG_MAG_CAL, 0, MSG_TYPE_SET_CAL_DATA);
msleep(20);
adsp_unicast(NULL, 0, MSG_MAG_CAL, 0, MSG_TYPE_FACTORY_DISABLE);
return size;
}
static ssize_t mag_selttest_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
int retry = 0, i;
RETRY_MAG_SELFTEST:
pr_info("[FACTORY] %s - start", __func__);
adsp_unicast(NULL, 0, MSG_MAG, 0, MSG_TYPE_ST_SHOW_DATA);
while (!(data->ready_flag[MSG_TYPE_ST_SHOW_DATA] & 1 << MSG_MAG) &&
cnt++ < TIMEOUT_CNT)
msleep(20);
data->ready_flag[MSG_TYPE_ST_SHOW_DATA] &= ~(1 << MSG_MAG);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
data->msg_buf[MSG_MAG][0] = -1;
}
if (!(data->msg_buf[MSG_MAG][0] == 0)) {
if (retry < MAG_ST_TRY_CNT) {
retry++;
for (i = 0; i < 10; i++)
data->msg_buf[MSG_MAG][i] = 0;
msleep(100);
pr_info("[FACTORY] %s - retry %d", __func__, retry);
goto RETRY_MAG_SELFTEST;
}
adsp_unicast(NULL, 0, MSG_MAG_CAL, 0, MSG_TYPE_FACTORY_ENABLE);
msleep(20);
adsp_unicast(NULL, 0, MSG_MAG_CAL, 0, MSG_TYPE_SET_CAL_DATA);
msleep(20);
adsp_unicast(NULL, 0, MSG_MAG_CAL, 0, MSG_TYPE_FACTORY_DISABLE);
return snprintf(buf, PAGE_SIZE, "-1,0,0,0,0,0,0,0,0,0\n");
}
pr_info("[FACTORY] status=%d, sf_status=%d, sf_x=%d, sf_y=%d, sf_z=%d\n dac=%d, adc=%d, adc_x=%d, adc_y=%d, adc_z=%d\n",
data->msg_buf[MSG_MAG][0], data->msg_buf[MSG_MAG][1],
data->msg_buf[MSG_MAG][2], data->msg_buf[MSG_MAG][3],
data->msg_buf[MSG_MAG][4], data->msg_buf[MSG_MAG][5],
data->msg_buf[MSG_MAG][6], data->msg_buf[MSG_MAG][7],
data->msg_buf[MSG_MAG][8], data->msg_buf[MSG_MAG][9]);
adsp_unicast(NULL, 0, MSG_MAG_CAL, 0, MSG_TYPE_FACTORY_ENABLE);
msleep(20);
adsp_unicast(NULL, 0, MSG_MAG_CAL, 0, MSG_TYPE_SET_CAL_DATA);
msleep(20);
adsp_unicast(NULL, 0, MSG_MAG_CAL, 0, MSG_TYPE_FACTORY_DISABLE);
return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
data->msg_buf[MSG_MAG][0], data->msg_buf[MSG_MAG][1],
data->msg_buf[MSG_MAG][2], data->msg_buf[MSG_MAG][3],
data->msg_buf[MSG_MAG][4], data->msg_buf[MSG_MAG][5],
data->msg_buf[MSG_MAG][6], data->msg_buf[MSG_MAG][7],
data->msg_buf[MSG_MAG][8], data->msg_buf[MSG_MAG][9]);
}
static ssize_t mag_dhr_sensor_info_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
#if 0
struct adsp_data *data = dev_get_drvdata(dev);
struct msg_data message;
uint8_t cnt = 0;
message.msg_type = MSG_MAG;
data->calib_ready_flag &= ~(1 << MSG_MAG);
adsp_unicast(&message, sizeof(message),
MSG_TYPE_GET_CALIB_DATA, 0, 0);
while (!(data->calib_ready_flag & 1 << MSG_MAG) &&
cnt++ < TIMEOUT_CNT)
msleep(20);
data->calib_ready_flag &= ~(1 << MSG_MAG);
if (cnt >= TIMEOUT_CNT)
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
pr_info("[FACTORY] %s\n", __func__);
pr_info("[FACTORY] 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n",
data->sensor_calib_data[MSG_MAG].si_mat[0],
data->sensor_calib_data[MSG_MAG].si_mat[1],
data->sensor_calib_data[MSG_MAG].si_mat[2],
data->sensor_calib_data[MSG_MAG].si_mat[3],
data->sensor_calib_data[MSG_MAG].si_mat[4],
data->sensor_calib_data[MSG_MAG].si_mat[5],
data->sensor_calib_data[MSG_MAG].si_mat[6],
data->sensor_calib_data[MSG_MAG].si_mat[7],
data->sensor_calib_data[MSG_MAG].si_mat[8]);
return snprintf(buf, PAGE_SIZE,
"\"SI_PARAMETER\":\"0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\"\n",
data->sensor_calib_data[MSG_MAG].si_mat[0],
data->sensor_calib_data[MSG_MAG].si_mat[1],
data->sensor_calib_data[MSG_MAG].si_mat[2],
data->sensor_calib_data[MSG_MAG].si_mat[3],
data->sensor_calib_data[MSG_MAG].si_mat[4],
data->sensor_calib_data[MSG_MAG].si_mat[5],
data->sensor_calib_data[MSG_MAG].si_mat[6],
data->sensor_calib_data[MSG_MAG].si_mat[7],
data->sensor_calib_data[MSG_MAG].si_mat[8]);
#endif
return 0;
}
static DEVICE_ATTR(name, 0444, mag_name_show, NULL);
static DEVICE_ATTR(vendor, 0444, mag_vendor_show, NULL);
static DEVICE_ATTR(raw_data, 0664, mag_raw_data_show, mag_raw_data_store);
static DEVICE_ATTR(adc, 0444, mag_raw_data_show, NULL);
static DEVICE_ATTR(dac, 0444, mag_check_cntl, NULL);
static DEVICE_ATTR(chk_registers, 0444, mag_check_registers, NULL);
static DEVICE_ATTR(selftest, 0440, mag_selttest_show, NULL);
static DEVICE_ATTR(asa, 0444, mag_get_asa, NULL);
static DEVICE_ATTR(status, 0444, mag_get_status, NULL);
static DEVICE_ATTR(dhr_sensor_info, 0440, mag_dhr_sensor_info_show, NULL);
static struct device_attribute *mag_attrs[] = {
&dev_attr_name,
&dev_attr_vendor,
&dev_attr_raw_data,
&dev_attr_adc,
&dev_attr_dac,
&dev_attr_chk_registers,
&dev_attr_selftest,
&dev_attr_asa,
&dev_attr_status,
&dev_attr_dhr_sensor_info,
NULL,
};
static int __init ak09918_factory_init(void)
{
adsp_factory_register(MSG_MAG, mag_attrs);
pr_info("[FACTORY] %s\n", __func__);
return 0;
}
static void __exit ak09918_factory_exit(void)
{
adsp_factory_unregister(MSG_MAG);
pr_info("[FACTORY] %s\n", __func__);
}
module_init(ak09918_factory_init);
module_exit(ak09918_factory_exit);

View File

@@ -0,0 +1,557 @@
/*
* Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 "adsp.h"
#ifdef CONFIG_SLPI_MOTOR
#include <linux/adsp/slpi_motor.h>
#endif
#define VENDOR "TDK"
#define CHIP_ID "ICM42605"
#define ACCEL_ST_TRY_CNT 3
#define ACCEL_FACTORY_CAL_CNT 20
#define ACCEL_RAW_DATA_CNT 3
#define MAX_ACCEL_1G 4096
/* Haptic Pattern A vibrate during 7ms.
* touch, touchkey, operation feedback use this.
* Do not call motor_workfunc when duration is 7ms.
*/
#define DURATION_SKIP 10
#define MOTOR_OFF 0
#define ACCEL_FACTORY_CAL_PATH "/efs/FactoryApp/accel_factory_cal"
#ifdef CONFIG_SLPI_MOTOR
struct accel_motor_data {
struct workqueue_struct *slpi_motor_wq;
struct work_struct work_slpi_motor;
int motor_state;
};
struct accel_motor_data *pdata_motor;
#endif
struct accel_data {
struct work_struct work_accel;
struct workqueue_struct *accel_wq;
struct adsp_data *dev_data;
bool is_complete_cal;
int32_t raw_data[ACCEL_RAW_DATA_CNT];
int32_t avg_data[ACCEL_RAW_DATA_CNT];
};
static struct accel_data *pdata;
static ssize_t accel_vendor_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR);
}
static ssize_t accel_name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID);
}
static ssize_t sensor_type_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", "ADSP");
}
int get_accel_cal_data(int32_t *cal_data)
{
struct file *factory_cal_filp = NULL;
mm_segment_t old_fs;
int ret = 0;
old_fs = get_fs();
set_fs(KERNEL_DS);
factory_cal_filp = filp_open(ACCEL_FACTORY_CAL_PATH,
O_RDONLY, 0440);
if (IS_ERR(factory_cal_filp)) {
set_fs(old_fs);
ret = PTR_ERR(factory_cal_filp);
pr_err("[FACTORY] %s: open fail accel_factory_cal:%d\n",
__func__, ret);
return ret;
}
ret = vfs_read(factory_cal_filp, (char *)cal_data,
ACCEL_RAW_DATA_CNT * sizeof(int32_t), &factory_cal_filp->f_pos);
if (ret < 0) {
pr_err("[FACTORY] %s: fd read fail:%d\n", __func__, ret);
filp_close(factory_cal_filp, current->files);
set_fs(old_fs);
return ret;
}
filp_close(factory_cal_filp, current->files);
set_fs(old_fs);
return ret;
}
int set_accel_cal_data(int32_t *cal_data, bool first_booting)
{
struct file *factory_cal_filp = NULL;
mm_segment_t old_fs;
int flag, ret = 0;
umode_t mode = 0;
old_fs = get_fs();
set_fs(KERNEL_DS);
if (first_booting) {
flag = O_TRUNC | O_RDWR | O_CREAT;
mode = 0600;
} else {
flag = O_RDWR;
mode = 0660;
}
factory_cal_filp = filp_open(ACCEL_FACTORY_CAL_PATH, flag, mode);
if (IS_ERR(factory_cal_filp)) {
set_fs(old_fs);
ret = PTR_ERR(factory_cal_filp);
pr_err("[FACTORY] %s: open fail accel_factory_cal:%d\n",
__func__, ret);
return ret;
}
ret = vfs_write(factory_cal_filp, (char *)cal_data,
ACCEL_RAW_DATA_CNT * sizeof(int32_t), &factory_cal_filp->f_pos);
if (ret < 0)
pr_err("[FACTORY] %s: fd write %d\n", __func__, ret);
filp_close(factory_cal_filp, current->files);
set_fs(old_fs);
adsp_unicast(pdata->avg_data, sizeof(pdata->avg_data),
MSG_ACCEL, 0, MSG_TYPE_SET_CAL_DATA);
return ret;
}
static ssize_t accel_calibration_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int32_t cal_data[ACCEL_RAW_DATA_CNT] = {0, };
if (get_accel_cal_data(cal_data) > 0) {
pr_info("[FACTORY] %s: %d, %d, %d\n", __func__,
cal_data[0], cal_data[1], cal_data[2]);
if (cal_data[0] == 0 && cal_data[1] == 0 && cal_data[2] == 0)
return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d\n",
0, 0, 0, 0);
else
return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d\n",
true, cal_data[0], cal_data[1], cal_data[2]);
} else {
pr_err("[FACTORY] %s: get_accel_cal_data fail\n", __func__);
return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d\n", 0, 0, 0, 0);
}
}
static ssize_t accel_calibration_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct adsp_data *data = dev_get_drvdata(dev);
pdata->dev_data = data;
if (sysfs_streq(buf, "0")) {
mutex_lock(&data->accel_factory_mutex);
memset(pdata->avg_data, 0, sizeof(pdata->avg_data));
set_accel_cal_data(pdata->avg_data, false);
mutex_unlock(&data->accel_factory_mutex);
} else {
pdata->is_complete_cal = false;
queue_work(pdata->accel_wq, &pdata->work_accel);
while (pdata->is_complete_cal == false) {
pr_info("[FACTORY] %s: In factory cal\n", __func__);
msleep(20);
}
mutex_lock(&data->accel_factory_mutex);
set_accel_cal_data(pdata->avg_data, false);
mutex_unlock(&data->accel_factory_mutex);
}
return size;
}
static void accel_work_func(struct work_struct *work)
{
struct accel_data *data = container_of((struct work_struct *)work,
struct accel_data, work_accel);
int i;
mutex_lock(&data->dev_data->accel_factory_mutex);
memset(pdata->avg_data, 0, sizeof(pdata->avg_data));
adsp_unicast(pdata->avg_data, sizeof(pdata->avg_data),
MSG_ACCEL, 0, MSG_TYPE_SET_CAL_DATA);
msleep(30); /* for init of bias */
for (i = 0; i < ACCEL_FACTORY_CAL_CNT; i++) {
msleep(20);
get_accel_raw_data(pdata->raw_data);
pdata->avg_data[0] += pdata->raw_data[0];
pdata->avg_data[1] += pdata->raw_data[1];
pdata->avg_data[2] += pdata->raw_data[2];
pr_info("[FACTORY] %s: %d, %d, %d\n", __func__,
pdata->raw_data[0], pdata->raw_data[1], pdata->raw_data[2]);
}
for (i = 0; i < ACCEL_RAW_DATA_CNT; i++) {
pdata->avg_data[i] /= ACCEL_FACTORY_CAL_CNT;
pr_err("[FACTORY] %s: avg : %d\n", __func__, pdata->avg_data[i]);
}
if (pdata->avg_data[2] > 0)
pdata->avg_data[2] -= MAX_ACCEL_1G;
else if (pdata->avg_data[2] < 0)
pdata->avg_data[2] += MAX_ACCEL_1G;
mutex_unlock(&data->dev_data->accel_factory_mutex);
pdata->is_complete_cal = true;
return;
}
static ssize_t accel_selftest_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
adsp_unicast(NULL, 0, MSG_ACCEL, 0, MSG_TYPE_ST_SHOW_DATA);
while (!(data->ready_flag[MSG_TYPE_ST_SHOW_DATA] & 1 << MSG_ACCEL) &&
cnt++ < TIMEOUT_CNT)
msleep(20);
data->ready_flag[MSG_TYPE_ST_SHOW_DATA] &= ~(1 << MSG_ACCEL);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
data->msg_buf[MSG_ACCEL][1] = -1;
}
pr_info("[FACTORY] %s : init = %d, result = %d, XYZ = %d, %d, %d, nXYZ = %d, %d, %d\n",
__func__, data->msg_buf[MSG_ACCEL][0],
data->msg_buf[MSG_ACCEL][7], data->msg_buf[MSG_ACCEL][1],
data->msg_buf[MSG_ACCEL][2], data->msg_buf[MSG_ACCEL][3],
data->msg_buf[MSG_ACCEL][4], data->msg_buf[MSG_ACCEL][5],
data->msg_buf[MSG_ACCEL][6]);
if (data->msg_buf[MSG_ACCEL][7] == 0) {
pr_info("[FACTORY] %s : Pass - result = %d\n",
__func__, data->msg_buf[MSG_ACCEL][7]);
} else {
pr_err("[FACTORY] %s : Fail - result = %d\n",
__func__, data->msg_buf[MSG_ACCEL][7]);
}
return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d,%d\n",
data->msg_buf[MSG_ACCEL][7],
(int)abs(data->msg_buf[MSG_ACCEL][1]),
(int)abs(data->msg_buf[MSG_ACCEL][2]),
(int)abs(data->msg_buf[MSG_ACCEL][3]),
(int)abs(data->msg_buf[MSG_ACCEL][4]),
(int)abs(data->msg_buf[MSG_ACCEL][5]),
(int)abs(data->msg_buf[MSG_ACCEL][6]));
}
static ssize_t accel_raw_data_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
int32_t raw_data[ACCEL_RAW_DATA_CNT] = {0, };
mutex_lock(&data->accel_factory_mutex);
get_accel_raw_data(raw_data);
mutex_unlock(&data->accel_factory_mutex);
return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n",
raw_data[0], raw_data[1], raw_data[2]);
}
static ssize_t accel_reactive_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
bool success = false;
int32_t raw_data[ACCEL_RAW_DATA_CNT] = {0, };
mutex_lock(&data->accel_factory_mutex);
get_accel_raw_data(raw_data);
mutex_unlock(&data->accel_factory_mutex);
if (raw_data[0] != 0 || raw_data[1] != 0 || raw_data[2] != 0)
success = true;
return snprintf(buf, PAGE_SIZE, "%d\n", success);
}
static ssize_t accel_reactive_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
if (sysfs_streq(buf, "1"))
pr_info("[FACTORY]: %s - on\n", __func__);
else if (sysfs_streq(buf, "0"))
pr_info("[FACTORY]: %s - off\n", __func__);
else if (sysfs_streq(buf, "2"))
pr_info("[FACTORY]: %s - factory\n", __func__);
return size;
}
static ssize_t accel_lowpassfilter_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
int32_t msg_buf;
if (sysfs_streq(buf, "1")) {
msg_buf = 1;
} else if (sysfs_streq(buf, "0")) {
msg_buf = 0;
} else {
pr_info("[FACTORY] %s: wrong value\n", __func__);
return size;
}
mutex_lock(&data->accel_factory_mutex);
adsp_unicast(&msg_buf, sizeof(int32_t), MSG_ACCEL, 0, MSG_TYPE_SET_ACCEL_LPF);
while (!(data->ready_flag[MSG_TYPE_SET_ACCEL_LPF] & 1 << MSG_ACCEL) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_SET_ACCEL_LPF] &= ~(1 << MSG_ACCEL);
mutex_unlock(&data->accel_factory_mutex);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
return size;
}
pr_info("[FACTORY] %s: lpf_on_off done (%d)(0x%x)\n", __func__,
data->msg_buf[MSG_ACCEL][0], data->msg_buf[MSG_ACCEL][1]);
return size;
}
#ifdef CONFIG_SLPI_MOTOR
int setSensorCallback(int state, int duration)
{
if (duration > MOTOR_OFF && duration <= DURATION_SKIP)
return 0;
if (pdata_motor->motor_state != state) {
pr_info("[FACTORY] %s: state = %d, duration = %d\n",
__func__, pdata_motor->motor_state, duration);
pdata_motor->motor_state = state;
queue_work(pdata_motor->slpi_motor_wq, &pdata_motor->work_slpi_motor);
}
return 0;
}
void slpi_motor_work_func(struct work_struct *work)
{
#if 0
struct msg_data message;
int motor = 0;
if (pdata_motor->motor_state == 1) {
motor = MSG_TYPE_ACCEL_MOTOR_ON;
message.msg_type = MSG_ACCEL_MOT_ON;
} else if (pdata_motor->motor_state == 0) {
motor = MSG_TYPE_ACCEL_MOTOR_OFF;
message.msg_type = MSG_ACCEL_MOT_OFF;
}
pr_info("[FACTORY] %s: state = %d\n", __func__, pdata_motor->motor_state);
adsp_unicast(&message, sizeof(message), motor, 0, 0);
#endif
}
#endif
static ssize_t accel_dhr_sensor_info_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
char ctrl1_xl = 0;
uint8_t fullscale = 0;
int32_t *info = data->msg_buf[MSG_ACCEL];
adsp_unicast(NULL, 0, MSG_ACCEL, 0, MSG_TYPE_GET_DHR_INFO);
while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << MSG_ACCEL) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << MSG_ACCEL);
if (cnt >= TIMEOUT_CNT)
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
ctrl1_xl = *info;
ctrl1_xl &= 0xC;
switch (ctrl1_xl) {
case 0xC:
fullscale = 8;
break;
case 0x8:
fullscale = 4;
break;
case 0x4:
fullscale = 16;
break;
case 0:
fullscale = 2;
break;
default:
break;
}
pr_info("[FACTORY] %s: f/s %u\n", __func__, fullscale);
return snprintf(buf, PAGE_SIZE, "\"FULL_SCALE\":\"%uG\"\n", fullscale);
}
static DEVICE_ATTR(name, 0444, accel_name_show, NULL);
static DEVICE_ATTR(vendor, 0444, accel_vendor_show, NULL);
static DEVICE_ATTR(type, 0444, sensor_type_show, NULL);
static DEVICE_ATTR(calibration, 0664,
accel_calibration_show, accel_calibration_store);
static DEVICE_ATTR(selftest, 0440,
accel_selftest_show, NULL);
static DEVICE_ATTR(raw_data, 0444, accel_raw_data_show, NULL);
static DEVICE_ATTR(reactive_alert, 0664,
accel_reactive_show, accel_reactive_store);
static DEVICE_ATTR(lowpassfilter, 0220,
NULL, accel_lowpassfilter_store);
#ifdef CONFIG_SEC_FACTORY
static DEVICE_ATTR(dhr_sensor_info, 0444,
accel_dhr_sensor_info_show, NULL);
#else
static DEVICE_ATTR(dhr_sensor_info, 0440,
accel_dhr_sensor_info_show, NULL);
#endif
static struct device_attribute *acc_attrs[] = {
&dev_attr_name,
&dev_attr_vendor,
&dev_attr_type,
&dev_attr_calibration,
&dev_attr_selftest,
&dev_attr_raw_data,
&dev_attr_reactive_alert,
&dev_attr_lowpassfilter,
&dev_attr_dhr_sensor_info,
NULL,
};
void accel_factory_init_work(void)
{
struct file *cal_filp = NULL;
mm_segment_t old_fs;
int32_t zero_data[ACCEL_RAW_DATA_CNT] = {0, };
int ret = 0;
old_fs = get_fs();
set_fs(KERNEL_DS);
cal_filp = filp_open(ACCEL_FACTORY_CAL_PATH, O_RDONLY, 0440);
if (PTR_ERR(cal_filp) == -ENOENT || PTR_ERR(cal_filp) == -ENXIO) {
pr_info("[FACTORY] %s : no accel_factory_cal file\n", __func__);
set_fs(old_fs);
set_accel_cal_data(zero_data, true);
} else if (IS_ERR(cal_filp)) {
pr_err("[FACTORY]: %s - filp_open error\n", __func__);
set_fs(old_fs);
} else {
pr_info("[FACTORY] %s : already exist\n", __func__);
ret = vfs_read(cal_filp, (char *)zero_data,
ACCEL_RAW_DATA_CNT * sizeof(int32_t), &cal_filp->f_pos);
if (ret < 0) {
pr_err("[FACTORY] %s: fd read fail:%d\n", __func__, ret);
zero_data[0] = zero_data[1] = zero_data[2] = 0;
adsp_unicast(zero_data, sizeof(zero_data),
MSG_ACCEL, 0, MSG_TYPE_SET_CAL_DATA);
filp_close(cal_filp, current->files);
set_fs(old_fs);
return;
}
adsp_unicast(zero_data, sizeof(zero_data),
MSG_ACCEL, 0, MSG_TYPE_SET_CAL_DATA);
filp_close(cal_filp, current->files);
set_fs(old_fs);
}
}
static int __init lsm6dsl_accel_factory_init(void)
{
adsp_factory_register(MSG_ACCEL, acc_attrs);
#ifdef CONFIG_SLPI_MOTOR
pdata_motor = kzalloc(sizeof(*pdata_motor), GFP_KERNEL);
if (pdata_motor == NULL)
return -ENOMEM;
pdata_motor->slpi_motor_wq =
create_singlethread_workqueue("slpi_motor_wq");
if (pdata_motor->slpi_motor_wq == NULL) {
pr_err("[FACTORY]: %s - could not create motor wq\n", __func__);
kfree(pdata_motor);
return -ENOMEM;
}
INIT_WORK(&pdata_motor->work_slpi_motor, slpi_motor_work_func);
pdata_motor->motor_state = 0;
#endif
pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
pdata->accel_wq = create_singlethread_workqueue("accel_wq");
INIT_WORK(&pdata->work_accel, accel_work_func);
pr_info("[FACTORY] %s\n", __func__);
return 0;
}
static void __exit lsm6dsl_accel_factory_exit(void)
{
adsp_factory_unregister(MSG_ACCEL);
#ifdef CONFIG_SLPI_MOTOR
if (pdata_motor != NULL && pdata_motor->slpi_motor_wq != NULL) {
cancel_work_sync(&pdata_motor->work_slpi_motor);
destroy_workqueue(pdata_motor->slpi_motor_wq);
pdata_motor->slpi_motor_wq = NULL;
}
#endif
pr_info("[FACTORY] %s\n", __func__);
}
module_init(lsm6dsl_accel_factory_init);
module_exit(lsm6dsl_accel_factory_exit);

View File

@@ -0,0 +1,205 @@
/*
* Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 "adsp.h"
#define VENDOR "TDK"
#define CHIP_ID "ICM42605"
#define ST_PASS 1
#define ST_FAIL 0
#define ST_MAX_DPS 10
#define ST_MIN_DPS -10
#define ST_MIN_RATIO 50
/* Scale Factor = 32768lsb(MAX) / 250dps(ST_FS) */
#define ST_SENS_DEF 131
#define SELFTEST_REVISED 1
static ssize_t gyro_vendor_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR);
}
static ssize_t gyro_name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID);
}
static ssize_t selftest_revised_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", SELFTEST_REVISED);
}
static ssize_t gyro_power_off(struct device *dev,
struct device_attribute *attr, char *buf)
{
pr_info("[FACTORY]: %s\n", __func__);
return snprintf(buf, PAGE_SIZE, "%d\n", 1);
}
static ssize_t gyro_power_on(struct device *dev,
struct device_attribute *attr, char *buf)
{
pr_info("[FACTORY]: %s\n", __func__);
return snprintf(buf, PAGE_SIZE, "%d\n", 1);
}
static ssize_t gyro_temp_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
adsp_unicast(NULL, 0, MSG_GYRO_TEMP, 0, MSG_TYPE_GET_RAW_DATA);
while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_GYRO_TEMP)
&& cnt++ < TIMEOUT_CNT)
msleep(20);
data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_GYRO_TEMP);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
return snprintf(buf, PAGE_SIZE, "-99\n");
}
pr_info("[FACTORY] %s: gyro_temp = %d\n", __func__,
data->msg_buf[MSG_GYRO_TEMP][0]);
return snprintf(buf, PAGE_SIZE, "%d\n",
data->msg_buf[MSG_GYRO_TEMP][0]);
}
static ssize_t gyro_selftest_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
int st_nost_data[3] = {0,};
int st_data[3] = {0,};
int st_fifo_data[3] = {0,};
int st_zro_res = ST_FAIL;
int st_ratio_res = ST_FAIL;
pr_info("[FACTORY] %s - start", __func__);
adsp_unicast(NULL, 0, MSG_GYRO, 0, MSG_TYPE_ST_SHOW_DATA);
while (!(data->ready_flag[MSG_TYPE_ST_SHOW_DATA] & 1 << MSG_GYRO) &&
cnt++ < TIMEOUT_CNT)
msleep(20);
data->ready_flag[MSG_TYPE_ST_SHOW_DATA] &= ~(1 << MSG_GYRO);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
return snprintf(buf, PAGE_SIZE,
"0,0,0,0,0,0,0,0,0,0,0,0,%d,%d\n",
ST_FAIL, ST_FAIL);
}
/* TDK : selftest fifo data check */
st_fifo_data[0] = data->msg_buf[MSG_GYRO][2] / ST_SENS_DEF;
st_fifo_data[1] = data->msg_buf[MSG_GYRO][3] / ST_SENS_DEF;
st_fifo_data[2] = data->msg_buf[MSG_GYRO][4] / ST_SENS_DEF;
/* TDK : selftest zro(NOST) check */
st_nost_data[0] = data->msg_buf[MSG_GYRO][6] / ST_SENS_DEF;
st_nost_data[1] = data->msg_buf[MSG_GYRO][7] / ST_SENS_DEF;
st_nost_data[2] = data->msg_buf[MSG_GYRO][8] / ST_SENS_DEF;
if((ST_MIN_DPS <= st_nost_data[0]) && (st_nost_data[0] <= ST_MAX_DPS)
&& (ST_MIN_DPS <= st_nost_data[1]) && (st_nost_data[1] <= ST_MAX_DPS)
&& (ST_MIN_DPS <= st_nost_data[2]) && (st_nost_data[2] <= ST_MAX_DPS))
st_zro_res = ST_PASS;
/* TDK : selftest ST data */
st_data[0] = data->msg_buf[MSG_GYRO][9] / ST_SENS_DEF;
st_data[1] = data->msg_buf[MSG_GYRO][10] / ST_SENS_DEF;
st_data[2] = data->msg_buf[MSG_GYRO][11] / ST_SENS_DEF;
/* TDK : selftest ratio check */
if(ST_MIN_RATIO < data->msg_buf[MSG_GYRO][12]
&& ST_MIN_RATIO < data->msg_buf[MSG_GYRO][13]
&& ST_MIN_RATIO < data->msg_buf[MSG_GYRO][14])
st_ratio_res = ST_PASS;
if (data->msg_buf[MSG_GYRO][1] == ST_FAIL) {
pr_info("[FACTORY]: %s - %d,%d,%d\n", __func__,
st_fifo_data[0], st_fifo_data[1], st_fifo_data[2]);
return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n",
st_fifo_data[0], st_fifo_data[1], st_fifo_data[2]);
}
pr_info("[FACTORY]: %s - %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
__func__,
st_fifo_data[0], st_fifo_data[1], st_fifo_data[2],
st_nost_data[0], st_nost_data[1], st_nost_data[2],
st_data[0], st_data[1], st_data[2],
data->msg_buf[MSG_GYRO][12],
data->msg_buf[MSG_GYRO][13], data->msg_buf[MSG_GYRO][14],
st_ratio_res, st_zro_res);
return snprintf(buf, PAGE_SIZE,
"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
st_fifo_data[0], st_fifo_data[1], st_fifo_data[2],
st_nost_data[0], st_nost_data[1], st_nost_data[2],
st_data[0], st_data[1], st_data[2],
data->msg_buf[MSG_GYRO][12],
data->msg_buf[MSG_GYRO][13], data->msg_buf[MSG_GYRO][14],
st_ratio_res, st_zro_res);
}
static DEVICE_ATTR(name, 0444, gyro_name_show, NULL);
static DEVICE_ATTR(vendor, 0444, gyro_vendor_show, NULL);
static DEVICE_ATTR(selftest, 0440, gyro_selftest_show, NULL);
static DEVICE_ATTR(power_on, 0444, gyro_power_on, NULL);
static DEVICE_ATTR(power_off, 0444, gyro_power_off, NULL);
static DEVICE_ATTR(temperature, 0440, gyro_temp_show, NULL);
static DEVICE_ATTR(selftest_revised, 0440, selftest_revised_show, NULL);
static struct device_attribute *gyro_attrs[] = {
&dev_attr_name,
&dev_attr_vendor,
&dev_attr_selftest,
&dev_attr_power_on,
&dev_attr_power_off,
&dev_attr_temperature,
&dev_attr_selftest_revised,
NULL,
};
static int __init lsm6dsl_gyro_factory_init(void)
{
adsp_factory_register(MSG_GYRO, gyro_attrs);
pr_info("[FACTORY] %s\n", __func__);
return 0;
}
static void __exit lsm6dsl_gyro_factory_exit(void)
{
adsp_factory_unregister(MSG_GYRO);
pr_info("[FACTORY] %s\n", __func__);
}
module_init(lsm6dsl_gyro_factory_init);
module_exit(lsm6dsl_gyro_factory_exit);

View File

@@ -0,0 +1,235 @@
/*
* Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 "adsp.h"
#define VENDOR "STM"
#define CHIP_ID "LPS22HH"
#define CALIBRATION_FILE_PATH "/efs/FactoryApp/baro_delta"
#define PR_MAX 8388607 /* 24 bit 2'compl */
#define PR_MIN -8388608
#define SNS_SUCCESS 0
#define ST_PASS 1
#define ST_FAIL 0
static int sea_level_pressure;
static int pressure_cal;
static ssize_t pressure_vendor_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR);
}
static ssize_t pressure_name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID);
}
static ssize_t sea_level_pressure_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", sea_level_pressure);
}
static ssize_t sea_level_pressure_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
if (sscanf(buf, "%10d", &sea_level_pressure) != 1) {
pr_err("[FACTORY] %s: sscanf error\n", __func__);
return size;
}
sea_level_pressure = sea_level_pressure / 100;
pr_info("[FACTORY] %s: sea_level_pressure = %d\n", __func__,
sea_level_pressure);
return size;
}
/*
int pressure_open_calibration(struct adsp_data *data)
{
int error = 0;
return error;
}
*/
static ssize_t pressure_cabratioin_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int temp = 0, error = 0;
error = kstrtoint(buf, 10, &temp);
if (error < 0) {
pr_err("[FACTORY] %s : kstrtoint failed.(%d)", __func__, error);
return error;
}
if (temp < PR_MIN || temp > PR_MAX)
return -EINVAL;
pressure_cal = temp;
return size;
}
static ssize_t pressure_cabratioin_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
//struct adsp_data *data = dev_get_drvdata(dev);
//pressure_open_calibration(data);
return snprintf(buf, PAGE_SIZE, "%d\n", pressure_cal);
}
static ssize_t temperature_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
adsp_unicast(NULL, 0, MSG_PRESSURE_TEMP, 0, MSG_TYPE_GET_RAW_DATA);
while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] &
1 << MSG_PRESSURE_TEMP) && cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_PRESSURE_TEMP);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
return snprintf(buf, PAGE_SIZE, "-99\n");
}
return snprintf(buf, PAGE_SIZE, "%d\n",
data->msg_buf[MSG_PRESSURE_TEMP][0]);
}
static ssize_t selftest_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
adsp_unicast(NULL, 0, MSG_PRESSURE, 0, MSG_TYPE_ST_SHOW_DATA);
while (!(data->ready_flag[MSG_TYPE_ST_SHOW_DATA] &
1 << MSG_PRESSURE) && cnt++ < TIMEOUT_CNT)
msleep(20);
data->ready_flag[MSG_TYPE_ST_SHOW_DATA] &= ~(1 << MSG_PRESSURE);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
return snprintf(buf, PAGE_SIZE, "0\n");
}
pr_info("[FACTORY] %s : P:%d, T:%d, RES:%d\n",
__func__, data->msg_buf[MSG_PRESSURE][0],
data->msg_buf[MSG_PRESSURE][1], data->msg_buf[MSG_PRESSURE][2]);
if (SNS_SUCCESS == data->msg_buf[MSG_PRESSURE][2])
return snprintf(buf, PAGE_SIZE, "%d\n", ST_PASS);
else
return snprintf(buf, PAGE_SIZE, "%d\n", ST_FAIL);
}
static ssize_t pressure_dhr_sensor_info_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
adsp_unicast(NULL, 0, MSG_PRESSURE, 0, MSG_TYPE_GET_DHR_INFO);
while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << MSG_PRESSURE) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << MSG_PRESSURE);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
} else {
pr_info("[FACTORY] %s - THS_P_L/H: %02x/%02x, WHOAMI: %02x, CTRL1/2/3: %02x/%02x/%02x, STATUS: %02x, DATA: %02x/%02x/%02x/%02x/%02x\n",
__func__,
data->msg_buf[MSG_PRESSURE][0], data->msg_buf[MSG_PRESSURE][1],
data->msg_buf[MSG_PRESSURE][2], data->msg_buf[MSG_PRESSURE][9],
data->msg_buf[MSG_PRESSURE][3], data->msg_buf[MSG_PRESSURE][4],
data->msg_buf[MSG_PRESSURE][5], data->msg_buf[MSG_PRESSURE][6],
data->msg_buf[MSG_PRESSURE][7], data->msg_buf[MSG_PRESSURE][8],
data->msg_buf[MSG_PRESSURE][10], data->msg_buf[MSG_PRESSURE][11]);
pr_info("[FACTORY] %s - 4Fh:%02x, 50h:%02x, 51h:%02x, 52h:%02x, 53h:%02x, 54h:%02x, 4Dh:%02x, 4Ah:%02x",
__func__,
data->msg_buf[MSG_PRESSURE][12], data->msg_buf[MSG_PRESSURE][13],
data->msg_buf[MSG_PRESSURE][14], data->msg_buf[MSG_PRESSURE][15],
data->msg_buf[MSG_PRESSURE][16], data->msg_buf[MSG_PRESSURE][17],
data->msg_buf[MSG_PRESSURE][18], data->msg_buf[MSG_PRESSURE][19]);
}
return snprintf(buf, PAGE_SIZE, "%s\n", "Done");
}
static DEVICE_ATTR(vendor, 0444, pressure_vendor_show, NULL);
static DEVICE_ATTR(name, 0444, pressure_name_show, NULL);
static DEVICE_ATTR(calibration, 0664,
pressure_cabratioin_show, pressure_cabratioin_store);
static DEVICE_ATTR(sea_level_pressure, 0664,
sea_level_pressure_show, sea_level_pressure_store);
static DEVICE_ATTR(temperature, 0444, temperature_show, NULL);
static DEVICE_ATTR(selftest, 0444, selftest_show, NULL);
#ifdef CONFIG_SEC_FACTORY
static DEVICE_ATTR(dhr_sensor_info, 0444,
pressure_dhr_sensor_info_show, NULL);
#else
static DEVICE_ATTR(dhr_sensor_info, 0440,
pressure_dhr_sensor_info_show, NULL);
#endif
static struct device_attribute *pressure_attrs[] = {
&dev_attr_vendor,
&dev_attr_name,
&dev_attr_calibration,
&dev_attr_sea_level_pressure,
&dev_attr_temperature,
&dev_attr_selftest,
&dev_attr_dhr_sensor_info,
NULL,
};
static int __init lps22hh_pressure_factory_init(void)
{
adsp_factory_register(MSG_PRESSURE, pressure_attrs);
pr_info("[FACTORY] %s\n", __func__);
return 0;
}
static void __exit lps22hh_pressure_factory_exit(void)
{
adsp_factory_unregister(MSG_PRESSURE);
pr_info("[FACTORY] %s\n", __func__);
}
module_init(lps22hh_pressure_factory_init);
module_exit(lps22hh_pressure_factory_exit);

View File

@@ -0,0 +1,568 @@
/*
* Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 "adsp.h"
#ifdef CONFIG_SLPI_MOTOR
#include <linux/adsp/slpi_motor.h>
#endif
#define VENDOR "STM"
#define CHIP_ID "LSM6DSL"
#define ACCEL_ST_TRY_CNT 3
#define ACCEL_FACTORY_CAL_CNT 20
#define ACCEL_RAW_DATA_CNT 3
#define MAX_ACCEL_1G 4096
/* Haptic Pattern A vibrate during 7ms.
* touch, touchkey, operation feedback use this.
* Do not call motor_workfunc when duration is 7ms.
*/
#define DURATION_SKIP 10
#define MOTOR_OFF 0
#define ACCEL_FACTORY_CAL_PATH "/efs/FactoryApp/accel_factory_cal"
#ifdef CONFIG_SLPI_MOTOR
struct accel_motor_data {
struct workqueue_struct *slpi_motor_wq;
struct work_struct work_slpi_motor;
int motor_state;
};
struct accel_motor_data *pdata_motor;
#endif
struct accel_data {
struct work_struct work_accel;
struct workqueue_struct *accel_wq;
struct adsp_data *dev_data;
bool is_complete_cal;
int32_t raw_data[ACCEL_RAW_DATA_CNT];
int32_t avg_data[ACCEL_RAW_DATA_CNT];
};
static struct accel_data *pdata;
static ssize_t accel_vendor_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR);
}
static ssize_t accel_name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID);
}
static ssize_t sensor_type_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", "ADSP");
}
int get_accel_cal_data(int32_t *cal_data)
{
struct file *factory_cal_filp = NULL;
mm_segment_t old_fs;
int ret = 0;
old_fs = get_fs();
set_fs(KERNEL_DS);
factory_cal_filp = filp_open(ACCEL_FACTORY_CAL_PATH,
O_RDONLY, 0440);
if (IS_ERR(factory_cal_filp)) {
set_fs(old_fs);
ret = PTR_ERR(factory_cal_filp);
pr_err("[FACTORY] %s: open fail accel_factory_cal:%d\n",
__func__, ret);
return ret;
}
ret = vfs_read(factory_cal_filp, (char *)cal_data,
ACCEL_RAW_DATA_CNT * sizeof(int32_t), &factory_cal_filp->f_pos);
if (ret < 0) {
pr_err("[FACTORY] %s: fd read fail:%d\n", __func__, ret);
filp_close(factory_cal_filp, current->files);
set_fs(old_fs);
return ret;
}
filp_close(factory_cal_filp, current->files);
set_fs(old_fs);
return ret;
}
int set_accel_cal_data(int32_t *cal_data, bool first_booting)
{
struct file *factory_cal_filp = NULL;
mm_segment_t old_fs;
int flag, ret = 0;
umode_t mode = 0;
old_fs = get_fs();
set_fs(KERNEL_DS);
if (first_booting) {
flag = O_TRUNC | O_RDWR | O_CREAT;
mode = 0600;
} else {
flag = O_RDWR;
mode = 0660;
}
factory_cal_filp = filp_open(ACCEL_FACTORY_CAL_PATH, flag, mode);
if (IS_ERR(factory_cal_filp)) {
set_fs(old_fs);
ret = PTR_ERR(factory_cal_filp);
pr_err("[FACTORY] %s: open fail accel_factory_cal:%d\n",
__func__, ret);
return ret;
}
ret = vfs_write(factory_cal_filp, (char *)cal_data,
ACCEL_RAW_DATA_CNT * sizeof(int32_t), &factory_cal_filp->f_pos);
if (ret < 0)
pr_err("[FACTORY] %s: fd write %d\n", __func__, ret);
filp_close(factory_cal_filp, current->files);
set_fs(old_fs);
adsp_unicast(pdata->avg_data, sizeof(pdata->avg_data),
MSG_ACCEL, 0, MSG_TYPE_SET_CAL_DATA);
return ret;
}
static ssize_t accel_calibration_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int32_t cal_data[ACCEL_RAW_DATA_CNT] = {0, };
if (get_accel_cal_data(cal_data) > 0) {
pr_info("[FACTORY] %s: %d, %d, %d\n", __func__,
cal_data[0], cal_data[1], cal_data[2]);
if (cal_data[0] == 0 && cal_data[1] == 0 && cal_data[2] == 0)
return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d\n",
0, 0, 0, 0);
else
return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d\n",
true, cal_data[0], cal_data[1], cal_data[2]);
} else {
pr_err("[FACTORY] %s: get_accel_cal_data fail\n", __func__);
return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d\n", 0, 0, 0, 0);
}
}
static ssize_t accel_calibration_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct adsp_data *data = dev_get_drvdata(dev);
pdata->dev_data = data;
if (sysfs_streq(buf, "0")) {
mutex_lock(&data->accel_factory_mutex);
memset(pdata->avg_data, 0, sizeof(pdata->avg_data));
set_accel_cal_data(pdata->avg_data, false);
mutex_unlock(&data->accel_factory_mutex);
} else {
pdata->is_complete_cal = false;
queue_work(pdata->accel_wq, &pdata->work_accel);
while (pdata->is_complete_cal == false) {
pr_info("[FACTORY] %s: In factory cal\n", __func__);
msleep(20);
}
mutex_lock(&data->accel_factory_mutex);
set_accel_cal_data(pdata->avg_data, false);
mutex_unlock(&data->accel_factory_mutex);
}
return size;
}
static void accel_work_func(struct work_struct *work)
{
struct accel_data *data = container_of((struct work_struct *)work,
struct accel_data, work_accel);
int i;
mutex_lock(&data->dev_data->accel_factory_mutex);
memset(pdata->avg_data, 0, sizeof(pdata->avg_data));
adsp_unicast(pdata->avg_data, sizeof(pdata->avg_data),
MSG_ACCEL, 0, MSG_TYPE_SET_CAL_DATA);
msleep(30); /* for init of bias */
for (i = 0; i < ACCEL_FACTORY_CAL_CNT; i++) {
msleep(20);
get_accel_raw_data(pdata->raw_data);
pdata->avg_data[0] += pdata->raw_data[0];
pdata->avg_data[1] += pdata->raw_data[1];
pdata->avg_data[2] += pdata->raw_data[2];
pr_info("[FACTORY] %s: %d, %d, %d\n", __func__,
pdata->raw_data[0], pdata->raw_data[1], pdata->raw_data[2]);
}
for (i = 0; i < ACCEL_RAW_DATA_CNT; i++) {
pdata->avg_data[i] /= ACCEL_FACTORY_CAL_CNT;
pr_err("[FACTORY] %s: avg : %d\n", __func__, pdata->avg_data[i]);
}
if (pdata->avg_data[2] > 0)
pdata->avg_data[2] -= MAX_ACCEL_1G;
else if (pdata->avg_data[2] < 0)
pdata->avg_data[2] += MAX_ACCEL_1G;
mutex_unlock(&data->dev_data->accel_factory_mutex);
pdata->is_complete_cal = true;
return;
}
static ssize_t accel_selftest_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
int retry = 0;
RETRY_ACCEL_SELFTEST:
adsp_unicast(NULL, 0, MSG_ACCEL, 0, MSG_TYPE_ST_SHOW_DATA);
while (!(data->ready_flag[MSG_TYPE_ST_SHOW_DATA] & 1 << MSG_ACCEL) &&
cnt++ < TIMEOUT_CNT)
msleep(20);
data->ready_flag[MSG_TYPE_ST_SHOW_DATA] &= ~(1 << MSG_ACCEL);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
data->msg_buf[MSG_ACCEL][1] = -1;
}
pr_info("[FACTORY] %s : init = %d, result = %d, XYZ = %d, %d, %d, nXYZ = %d, %d, %d\n",
__func__, data->msg_buf[MSG_ACCEL][0],
data->msg_buf[MSG_ACCEL][1], data->msg_buf[MSG_ACCEL][2],
data->msg_buf[MSG_ACCEL][3], data->msg_buf[MSG_ACCEL][4],
data->msg_buf[MSG_ACCEL][5], data->msg_buf[MSG_ACCEL][6],
data->msg_buf[MSG_ACCEL][7]);
if (data->msg_buf[MSG_ACCEL][1] == 1) {
pr_info("[FACTORY] %s : Pass - result = %d, retry = %d\n",
__func__, data->msg_buf[MSG_ACCEL][1], retry);
} else {
data->msg_buf[MSG_ACCEL][1] = -5;
pr_err("[FACTORY] %s : Fail - result = %d, retry = %d\n",
__func__, data->msg_buf[MSG_ACCEL][1], retry);
if (retry < ACCEL_ST_TRY_CNT &&
data->msg_buf[MSG_ACCEL][2] == 0) {
retry++;
msleep(200);
pr_info("[FACTORY] %s: retry\n", __func__);
goto RETRY_ACCEL_SELFTEST;
}
}
return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d,%d\n",
data->msg_buf[MSG_ACCEL][1],
(int)abs(data->msg_buf[MSG_ACCEL][2]),
(int)abs(data->msg_buf[MSG_ACCEL][3]),
(int)abs(data->msg_buf[MSG_ACCEL][4]),
(int)abs(data->msg_buf[MSG_ACCEL][5]),
(int)abs(data->msg_buf[MSG_ACCEL][6]),
(int)abs(data->msg_buf[MSG_ACCEL][7]));
}
static ssize_t accel_raw_data_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
int32_t raw_data[ACCEL_RAW_DATA_CNT] = {0, };
mutex_lock(&data->accel_factory_mutex);
get_accel_raw_data(raw_data);
mutex_unlock(&data->accel_factory_mutex);
return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n",
raw_data[0], raw_data[1], raw_data[2]);
}
static ssize_t accel_reactive_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
bool success = false;
int32_t raw_data[ACCEL_RAW_DATA_CNT] = {0, };
mutex_lock(&data->accel_factory_mutex);
get_accel_raw_data(raw_data);
mutex_unlock(&data->accel_factory_mutex);
if (raw_data[0] != 0 || raw_data[1] != 0 || raw_data[2] != 0)
success = true;
return snprintf(buf, PAGE_SIZE, "%d\n", success);
}
static ssize_t accel_reactive_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
if (sysfs_streq(buf, "1"))
pr_info("[FACTORY]: %s - on\n", __func__);
else if (sysfs_streq(buf, "0"))
pr_info("[FACTORY]: %s - off\n", __func__);
else if (sysfs_streq(buf, "2"))
pr_info("[FACTORY]: %s - factory\n", __func__);
return size;
}
static ssize_t accel_lowpassfilter_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
int32_t msg_buf;
if (sysfs_streq(buf, "1")) {
msg_buf = 1;
} else if (sysfs_streq(buf, "0")) {
msg_buf = 0;
} else {
pr_info("[FACTORY] %s: wrong value\n", __func__);
return size;
}
mutex_lock(&data->accel_factory_mutex);
adsp_unicast(&msg_buf, sizeof(int32_t), MSG_ACCEL, 0, MSG_TYPE_SET_ACCEL_LPF);
while (!(data->ready_flag[MSG_TYPE_SET_ACCEL_LPF] & 1 << MSG_ACCEL) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_SET_ACCEL_LPF] &= ~(1 << MSG_ACCEL);
mutex_unlock(&data->accel_factory_mutex);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
return size;
}
pr_info("[FACTORY] %s: lpf_on_off done (%d)(0x%x)\n", __func__,
data->msg_buf[MSG_ACCEL][0], data->msg_buf[MSG_ACCEL][1]);
return size;
}
#ifdef CONFIG_SLPI_MOTOR
int setSensorCallback(int state, int duration)
{
if (duration > MOTOR_OFF && duration <= DURATION_SKIP)
return 0;
if (pdata_motor->motor_state != state) {
pr_info("[FACTORY] %s: state = %d, duration = %d\n",
__func__, pdata_motor->motor_state, duration);
pdata_motor->motor_state = state;
queue_work(pdata_motor->slpi_motor_wq, &pdata_motor->work_slpi_motor);
}
return 0;
}
void slpi_motor_work_func(struct work_struct *work)
{
#if 0
struct msg_data message;
int motor = 0;
if (pdata_motor->motor_state == 1) {
motor = MSG_TYPE_ACCEL_MOTOR_ON;
message.msg_type = MSG_ACCEL_MOT_ON;
} else if (pdata_motor->motor_state == 0) {
motor = MSG_TYPE_ACCEL_MOTOR_OFF;
message.msg_type = MSG_ACCEL_MOT_OFF;
}
pr_info("[FACTORY] %s: state = %d\n", __func__, pdata_motor->motor_state);
adsp_unicast(&message, sizeof(message), motor, 0, 0);
#endif
}
#endif
static ssize_t accel_dhr_sensor_info_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
char ctrl1_xl = 0;
uint8_t fullscale = 0;
int32_t *info = data->msg_buf[MSG_ACCEL];
adsp_unicast(NULL, 0, MSG_ACCEL, 0, MSG_TYPE_GET_DHR_INFO);
while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << MSG_ACCEL) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << MSG_ACCEL);
if (cnt >= TIMEOUT_CNT)
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
ctrl1_xl = *info;
ctrl1_xl &= 0xC;
switch (ctrl1_xl) {
case 0xC:
fullscale = 8;
break;
case 0x8:
fullscale = 4;
break;
case 0x4:
fullscale = 16;
break;
case 0:
fullscale = 2;
break;
default:
break;
}
pr_info("[FACTORY] %s: f/s %u\n", __func__, fullscale);
return snprintf(buf, PAGE_SIZE, "\"FULL_SCALE\":\"%uG\"\n", fullscale);
}
static DEVICE_ATTR(name, 0444, accel_name_show, NULL);
static DEVICE_ATTR(vendor, 0444, accel_vendor_show, NULL);
static DEVICE_ATTR(type, 0444, sensor_type_show, NULL);
static DEVICE_ATTR(calibration, 0664,
accel_calibration_show, accel_calibration_store);
static DEVICE_ATTR(selftest, 0440,
accel_selftest_show, NULL);
static DEVICE_ATTR(raw_data, 0444, accel_raw_data_show, NULL);
static DEVICE_ATTR(reactive_alert, 0664,
accel_reactive_show, accel_reactive_store);
static DEVICE_ATTR(lowpassfilter, 0220,
NULL, accel_lowpassfilter_store);
#ifdef CONFIG_SEC_FACTORY
static DEVICE_ATTR(dhr_sensor_info, 0444,
accel_dhr_sensor_info_show, NULL);
#else
static DEVICE_ATTR(dhr_sensor_info, 0440,
accel_dhr_sensor_info_show, NULL);
#endif
static struct device_attribute *acc_attrs[] = {
&dev_attr_name,
&dev_attr_vendor,
&dev_attr_type,
&dev_attr_calibration,
&dev_attr_selftest,
&dev_attr_raw_data,
&dev_attr_reactive_alert,
&dev_attr_lowpassfilter,
&dev_attr_dhr_sensor_info,
NULL,
};
void accel_factory_init_work(void)
{
struct file *cal_filp = NULL;
mm_segment_t old_fs;
int32_t zero_data[ACCEL_RAW_DATA_CNT] = {0, };
int ret = 0;
old_fs = get_fs();
set_fs(KERNEL_DS);
cal_filp = filp_open(ACCEL_FACTORY_CAL_PATH, O_RDONLY, 0440);
if (PTR_ERR(cal_filp) == -ENOENT || PTR_ERR(cal_filp) == -ENXIO) {
pr_info("[FACTORY] %s : no accel_factory_cal file\n", __func__);
set_fs(old_fs);
set_accel_cal_data(zero_data, true);
} else if (IS_ERR(cal_filp)) {
pr_err("[FACTORY]: %s - filp_open error\n", __func__);
set_fs(old_fs);
} else {
pr_info("[FACTORY] %s : already exist\n", __func__);
ret = vfs_read(cal_filp, (char *)zero_data,
ACCEL_RAW_DATA_CNT * sizeof(int32_t), &cal_filp->f_pos);
if (ret < 0) {
pr_err("[FACTORY] %s: fd read fail:%d\n", __func__, ret);
zero_data[0] = zero_data[1] = zero_data[2] = 0;
adsp_unicast(zero_data, sizeof(zero_data),
MSG_ACCEL, 0, MSG_TYPE_SET_CAL_DATA);
filp_close(cal_filp, current->files);
set_fs(old_fs);
return;
}
adsp_unicast(zero_data, sizeof(zero_data),
MSG_ACCEL, 0, MSG_TYPE_SET_CAL_DATA);
filp_close(cal_filp, current->files);
set_fs(old_fs);
}
}
static int __init lsm6dsl_accel_factory_init(void)
{
adsp_factory_register(MSG_ACCEL, acc_attrs);
#ifdef CONFIG_SLPI_MOTOR
pdata_motor = kzalloc(sizeof(*pdata_motor), GFP_KERNEL);
if (pdata_motor == NULL)
return -ENOMEM;
pdata_motor->slpi_motor_wq =
create_singlethread_workqueue("slpi_motor_wq");
if (pdata_motor->slpi_motor_wq == NULL) {
pr_err("[FACTORY]: %s - could not create motor wq\n", __func__);
kfree(pdata_motor);
return -ENOMEM;
}
INIT_WORK(&pdata_motor->work_slpi_motor, slpi_motor_work_func);
pdata_motor->motor_state = 0;
#endif
pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
pdata->accel_wq = create_singlethread_workqueue("accel_wq");
INIT_WORK(&pdata->work_accel, accel_work_func);
pr_info("[FACTORY] %s\n", __func__);
return 0;
}
static void __exit lsm6dsl_accel_factory_exit(void)
{
adsp_factory_unregister(MSG_ACCEL);
#ifdef CONFIG_SLPI_MOTOR
if (pdata_motor != NULL && pdata_motor->slpi_motor_wq != NULL) {
cancel_work_sync(&pdata_motor->work_slpi_motor);
destroy_workqueue(pdata_motor->slpi_motor_wq);
pdata_motor->slpi_motor_wq = NULL;
}
#endif
pr_info("[FACTORY] %s\n", __func__);
}
module_init(lsm6dsl_accel_factory_init);
module_exit(lsm6dsl_accel_factory_exit);

View File

@@ -0,0 +1,194 @@
/*
* Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 "adsp.h"
#define VENDOR "STM"
#define CHIP_ID "LSM6DSL"
#define ST_PASS 1
#define ST_FAIL 0
#define ST_ZRO_MIN (-40)
#define ST_ZRO_MAX 40
#define SELFTEST_REVISED 1
static ssize_t gyro_vendor_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR);
}
static ssize_t gyro_name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID);
}
static ssize_t selftest_revised_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", SELFTEST_REVISED);
}
static ssize_t gyro_power_off(struct device *dev,
struct device_attribute *attr, char *buf)
{
pr_info("[FACTORY]: %s\n", __func__);
return snprintf(buf, PAGE_SIZE, "%d\n", 1);
}
static ssize_t gyro_power_on(struct device *dev,
struct device_attribute *attr, char *buf)
{
pr_info("[FACTORY]: %s\n", __func__);
return snprintf(buf, PAGE_SIZE, "%d\n", 1);
}
static ssize_t gyro_temp_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
adsp_unicast(NULL, 0, MSG_GYRO_TEMP, 0, MSG_TYPE_GET_RAW_DATA);
while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_GYRO_TEMP)
&& cnt++ < TIMEOUT_CNT)
msleep(20);
data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_GYRO_TEMP);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
return snprintf(buf, PAGE_SIZE, "-99\n");
}
pr_info("[FACTORY] %s: gyro_temp = %d\n", __func__,
data->msg_buf[MSG_GYRO_TEMP][0]);
return snprintf(buf, PAGE_SIZE, "%d\n",
data->msg_buf[MSG_GYRO_TEMP][0]);
}
static ssize_t gyro_selftest_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
int st_diff_res = ST_FAIL;
int st_zro_res = ST_FAIL;
pr_info("[FACTORY] %s - start", __func__);
adsp_unicast(NULL, 0, MSG_GYRO, 0, MSG_TYPE_ST_SHOW_DATA);
while (!(data->ready_flag[MSG_TYPE_ST_SHOW_DATA] & 1 << MSG_GYRO) &&
cnt++ < TIMEOUT_CNT)
msleep(20);
data->ready_flag[MSG_TYPE_ST_SHOW_DATA] &= ~(1 << MSG_GYRO);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
return snprintf(buf, PAGE_SIZE,
"0,0,0,0,0,0,0,0,0,0,0,0,%d,%d\n",
ST_FAIL, ST_FAIL);
}
if (data->msg_buf[MSG_GYRO][1] != 0) {
pr_info("[FACTORY] %s - failed(%d, %d)\n", __func__,
data->msg_buf[MSG_GYRO][1],
data->msg_buf[MSG_GYRO][5]);
pr_info("[FACTORY]: %s - %d,%d,%d\n", __func__,
data->msg_buf[MSG_GYRO][2],
data->msg_buf[MSG_GYRO][3],
data->msg_buf[MSG_GYRO][4]);
return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n",
data->msg_buf[MSG_GYRO][2],
data->msg_buf[MSG_GYRO][3],
data->msg_buf[MSG_GYRO][4]);
}
if (!data->msg_buf[MSG_GYRO][5])
st_diff_res = ST_PASS;
if((ST_ZRO_MIN <= data->msg_buf[MSG_GYRO][6])
&& (data->msg_buf[MSG_GYRO][6] <= ST_ZRO_MAX)
&& (ST_ZRO_MIN <= data->msg_buf[MSG_GYRO][7])
&& (data->msg_buf[MSG_GYRO][7] <= ST_ZRO_MAX)
&& (ST_ZRO_MIN <= data->msg_buf[MSG_GYRO][8])
&& (data->msg_buf[MSG_GYRO][8]<= ST_ZRO_MAX))
st_zro_res = ST_PASS;
pr_info("[FACTORY]: %s - %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
__func__,
data->msg_buf[MSG_GYRO][2], data->msg_buf[MSG_GYRO][3],
data->msg_buf[MSG_GYRO][4], data->msg_buf[MSG_GYRO][6],
data->msg_buf[MSG_GYRO][7], data->msg_buf[MSG_GYRO][8],
data->msg_buf[MSG_GYRO][9], data->msg_buf[MSG_GYRO][10],
data->msg_buf[MSG_GYRO][11], data->msg_buf[MSG_GYRO][12],
data->msg_buf[MSG_GYRO][13], data->msg_buf[MSG_GYRO][14],
st_diff_res, st_zro_res);
return snprintf(buf, PAGE_SIZE,
"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
data->msg_buf[MSG_GYRO][2], data->msg_buf[MSG_GYRO][3],
data->msg_buf[MSG_GYRO][4], data->msg_buf[MSG_GYRO][6],
data->msg_buf[MSG_GYRO][7], data->msg_buf[MSG_GYRO][8],
data->msg_buf[MSG_GYRO][9], data->msg_buf[MSG_GYRO][10],
data->msg_buf[MSG_GYRO][11], data->msg_buf[MSG_GYRO][12],
data->msg_buf[MSG_GYRO][13], data->msg_buf[MSG_GYRO][14],
st_diff_res, st_zro_res);
}
static DEVICE_ATTR(name, 0444, gyro_name_show, NULL);
static DEVICE_ATTR(vendor, 0444, gyro_vendor_show, NULL);
static DEVICE_ATTR(selftest, 0440, gyro_selftest_show, NULL);
static DEVICE_ATTR(power_on, 0444, gyro_power_on, NULL);
static DEVICE_ATTR(power_off, 0444, gyro_power_off, NULL);
static DEVICE_ATTR(temperature, 0440, gyro_temp_show, NULL);
static DEVICE_ATTR(selftest_revised, 0440, selftest_revised_show, NULL);
static struct device_attribute *gyro_attrs[] = {
&dev_attr_name,
&dev_attr_vendor,
&dev_attr_selftest,
&dev_attr_power_on,
&dev_attr_power_off,
&dev_attr_temperature,
&dev_attr_selftest_revised,
NULL,
};
static int __init lsm6dsl_gyro_factory_init(void)
{
adsp_factory_register(MSG_GYRO, gyro_attrs);
pr_info("[FACTORY] %s\n", __func__);
return 0;
}
static void __exit lsm6dsl_gyro_factory_exit(void)
{
adsp_factory_unregister(MSG_GYRO);
pr_info("[FACTORY] %s\n", __func__);
}
module_init(lsm6dsl_gyro_factory_init);
module_exit(lsm6dsl_gyro_factory_exit);

View File

@@ -0,0 +1,342 @@
/*
* Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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/adsp/slpi_motor.h>
#include <linux/adsp/ssc_ssr_reason.h>
#ifdef CONFIG_SUPPORT_DEVICE_MODE
#include <linux/hall.h>
#endif
#include "adsp.h"
#define SSR_REASON_LEN 128
#ifdef CONFIG_SEC_FACTORY
#define SLPI_STUCK "SLPI_STUCK"
#define SLPI_PASS "SLPI_PASS"
#endif
static int pid;
static char panic_msg[SSR_REASON_LEN];
/*************************************************************************/
/* factory Sysfs */
/*************************************************************************/
static char operation_mode_flag[11];
static ssize_t dumpstate_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
int32_t type[1];
if (pid != 0) {
adsp_unicast(NULL, 0, MSG_ACCEL, 0, MSG_TYPE_GET_DHR_INFO);
while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << MSG_ACCEL) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << MSG_ACCEL);
if (cnt >= TIMEOUT_CNT)
pr_err("[FACTORY] %s: accel dhr_sensor_info Timeout!!!\n",
__func__);
adsp_unicast(NULL, 0, MSG_LIGHT, 0, MSG_TYPE_GET_DHR_INFO);
while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << MSG_LIGHT) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << MSG_LIGHT);
if (cnt >= TIMEOUT_CNT)
pr_err("[FACTORY] %s: light dhr_sensor_info Timeout!!!\n",
__func__);
pr_info("[FACTORY] to take the logs\n");
type[0] = 0;
} else {
type[0] = 2;
pr_info("[FACTORY] logging service was stopped %d\n", pid);
}
adsp_unicast(type, sizeof(type), MSG_SSC_CORE, 0, MSG_TYPE_DUMPSTATE);
return snprintf(buf, PAGE_SIZE, "SSC_CORE\n");
}
static ssize_t operation_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s", operation_mode_flag);
}
static ssize_t operation_mode_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int i;
for (i = 0; i < 10 && buf[i] != '\0'; i++)
operation_mode_flag[i] = buf[i];
operation_mode_flag[i] = '\0';
pr_info("[FACTORY] %s: operation_mode_flag = %s\n", __func__,
operation_mode_flag);
return size;
}
static ssize_t mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
unsigned long timeout;
unsigned long timeout_2;
int32_t type[1];
if (pid != 0) {
timeout = jiffies + (2 * HZ);
timeout_2 = jiffies + (10 * HZ);
type[0] = 1;
pr_info("[FACTORY] To stop logging %d\n", pid);
adsp_unicast(type, sizeof(type), MSG_SSC_CORE, 0, MSG_TYPE_DUMPSTATE);
while (pid != 0) {
msleep(25);
if (time_after(jiffies, timeout))
pr_info("[FACTORY] %s: Timeout!!!\n", __func__);
if (time_after(jiffies, timeout_2)) {
// panic("force crash : ssc core\n");
pr_info("[FACTORY] pid %d\n", pid);
return snprintf(buf, PAGE_SIZE, "1\n");
}
}
}
return snprintf(buf, PAGE_SIZE, "0\n");
}
static ssize_t mode_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int data = 0;
int32_t type[1];
if (kstrtoint(buf, 10, &data)) {
pr_err("[FACTORY] %s: kstrtoint fail\n", __func__);
return -EINVAL;
}
if (data != 1) {
pr_err("[FACTORY] %s: data was wrong\n", __func__);
return -EINVAL;
}
if (pid != 0) {
type[0] = 1;
adsp_unicast(type, sizeof(type), MSG_SSC_CORE, 0, MSG_TYPE_DUMPSTATE);
}
return size;
}
static ssize_t pid_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", pid);
}
static ssize_t pid_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int data = 0;
if (kstrtoint(buf, 10, &data)) {
pr_err("[FACTORY] %s: kstrtoint fail\n", __func__);
return -EINVAL;
}
pid = data;
pr_info("[FACTORY] %s: pid %d\n", __func__, pid);
return size;
}
static ssize_t remove_sensor_sysfs_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
unsigned int type = MSG_SENSOR_MAX;
struct adsp_data *data = dev_get_drvdata(dev);
if (kstrtouint(buf, 10, &type)) {
pr_err("[FACTORY] %s: kstrtouint fail\n", __func__);
return -EINVAL;
}
if (type > PHYSICAL_SENSOR_SYSFS) {
pr_err("[FACTORY] %s: Invalid type %u\n", __func__, type);
return size;
}
pr_info("[FACTORY] %s: type = %u\n", __func__, type);
mutex_lock(&data->remove_sysfs_mutex);
adsp_factory_unregister(type);
mutex_unlock(&data->remove_sysfs_mutex);
return size;
}
extern unsigned int sec_hw_rev(void);
int ssc_system_rev_test;
static ssize_t ssc_hw_rev_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
pr_info("[FACTORY] %s: ssc_rev:%d\n", __func__, sec_hw_rev());
if (!ssc_system_rev_test)
return snprintf(buf, PAGE_SIZE, "%d\n", sec_hw_rev());
else
return snprintf(buf, PAGE_SIZE, "%d\n", ssc_system_rev_test);
}
static ssize_t ssc_hw_rev_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
if (kstrtoint(buf, 10, &ssc_system_rev_test)) {
pr_err("[FACTORY] %s: kstrtoint fail\n", __func__);
return -EINVAL;
}
pr_info("[FACTORY] %s: system_rev_ssc %d\n", __func__, ssc_system_rev_test);
return size;
}
void ssr_reason_call_back(char reason[], int len)
{
if (len <= 0) {
pr_info("[FACTORY] ssr %d\n", len);
return;
}
memset(panic_msg, 0, SSR_REASON_LEN);
strlcpy(panic_msg, reason, min(len, (int)(SSR_REASON_LEN - 1)));
pr_info("[FACTORY] ssr %s\n", panic_msg);
}
static ssize_t ssr_msg_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
#ifdef CONFIG_SEC_FACTORY
struct adsp_data *data = dev_get_drvdata(dev);
int32_t raw_data_acc[3] = {0, };
int32_t raw_data_mag[3] = {0, };
int ret_acc = 0;
int ret_mag = 0;
mutex_lock(&data->accel_factory_mutex);
ret_acc = get_accel_raw_data(raw_data_acc);
mutex_unlock(&data->accel_factory_mutex);
ret_mag = get_mag_raw_data(raw_data_mag);
pr_info("[FACTORY] %s: accel(%d, %d, %d), mag(%d, %d, %d)\n", __func__,
raw_data_acc[0], raw_data_acc[1], raw_data_acc[2],
raw_data_mag[0], raw_data_mag[1], raw_data_mag[2]);
if (ret_acc == -1 && ret_mag == -1) {
pr_err("[FACTORY] %s: SLPI stuck, check hal log\n", __func__);
return snprintf(buf, PAGE_SIZE, "%s\n", SLPI_STUCK);
} else {
return snprintf(buf, PAGE_SIZE, "%s\n", SLPI_PASS);
}
#else
return snprintf(buf, PAGE_SIZE, "%s\n", panic_msg);
#endif
}
static ssize_t ssr_reset_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int32_t type[1];
type[0] = 0xff;
adsp_unicast(type, sizeof(type), MSG_SSC_CORE, 0, MSG_TYPE_DUMPSTATE);
pr_info("[FACTORY] %s\n", __func__);
return snprintf(buf, PAGE_SIZE, "%s\n", "Success");
}
#ifdef CONFIG_SUPPORT_DEVICE_MODE
int sns_device_mode_notify(struct notifier_block *nb,
unsigned long flip_state, void *v)
{
int state = (int)flip_state;
pr_info("[FACTORY] %s - device mode %d", __func__, state);
if(state == 0)
adsp_unicast(NULL, 0, MSG_SSC_CORE, 0, MSG_TYPE_FACTORY_ENABLE);
else
adsp_unicast(NULL, 0, MSG_SSC_CORE, 0, MSG_TYPE_FACTORY_DISABLE);
return 0;
}
static struct notifier_block sns_device_mode_notifier = {
.notifier_call = sns_device_mode_notify,
.priority = 1,
};
#endif
static DEVICE_ATTR(dumpstate, 0440, dumpstate_show, NULL);
static DEVICE_ATTR(operation_mode, 0664,
operation_mode_show, operation_mode_store);
static DEVICE_ATTR(mode, 0660, mode_show, mode_store);
static DEVICE_ATTR(ssc_pid, 0660, pid_show, pid_store);
static DEVICE_ATTR(remove_sysfs, 0220, NULL, remove_sensor_sysfs_store);
static DEVICE_ATTR(ssc_hw_rev, 0664, ssc_hw_rev_show, ssc_hw_rev_store);
static DEVICE_ATTR(ssr_msg, 0440, ssr_msg_show, NULL);
static DEVICE_ATTR(ssr_reset, 0440, ssr_reset_show, NULL);
static struct device_attribute *core_attrs[] = {
&dev_attr_dumpstate,
&dev_attr_operation_mode,
&dev_attr_mode,
&dev_attr_ssc_pid,
&dev_attr_remove_sysfs,
&dev_attr_ssc_hw_rev,
&dev_attr_ssr_msg,
&dev_attr_ssr_reset,
NULL,
};
static int __init core_factory_init(void)
{
adsp_factory_register(MSG_SSC_CORE, core_attrs);
#ifdef CONFIG_SUPPORT_DEVICE_MODE
hall_ic_register_notify(&sns_device_mode_notifier);
#endif
pr_info("[FACTORY] %s\n", __func__);
return 0;
}
static void __exit core_factory_exit(void)
{
adsp_factory_unregister(MSG_SSC_CORE);
#ifdef CONFIG_SUPPORT_DEVICE_MODE
hall_ic_unregister_notify(&sns_device_mode_notifier);
#endif
pr_info("[FACTORY] %s\n", __func__);
}
module_init(core_factory_init);
module_exit(core_factory_exit);

View File

@@ -0,0 +1,118 @@
/*
* Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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/dirent.h>
#include "adsp.h"
#define VENDOR "CAPELLA"
#define CHIP_ID "VCNL36658"
/*************************************************************************/
/* factory Sysfs */
/*************************************************************************/
static ssize_t light_vendor_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR);
}
static ssize_t light_name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID);
}
static ssize_t light_raw_data_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
mutex_lock(&data->light_factory_mutex);
adsp_unicast(NULL, 0, MSG_LIGHT, 0, MSG_TYPE_GET_RAW_DATA);
while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_LIGHT) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_LIGHT);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
mutex_unlock(&data->light_factory_mutex);
return snprintf(buf, PAGE_SIZE, "0,0,0,0,0,0\n");
}
mutex_unlock(&data->light_factory_mutex);
return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d\n",
data->msg_buf[MSG_LIGHT][0], data->msg_buf[MSG_LIGHT][1],
data->msg_buf[MSG_LIGHT][2], data->msg_buf[MSG_LIGHT][3],
data->msg_buf[MSG_LIGHT][4], data->msg_buf[MSG_LIGHT][5]);
}
static ssize_t light_get_dhr_sensor_info_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
pr_info("[FACTORY] %s: start\n", __func__);
mutex_lock(&data->light_factory_mutex);
adsp_unicast(NULL, 0, MSG_LIGHT, 0, MSG_TYPE_GET_DHR_INFO);
while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << MSG_LIGHT) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << MSG_LIGHT);
if (cnt >= TIMEOUT_CNT)
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
mutex_unlock(&data->light_factory_mutex);
return data->msg_buf[MSG_LIGHT][0];
}
static DEVICE_ATTR(vendor, 0444, light_vendor_show, NULL);
static DEVICE_ATTR(name, 0444, light_name_show, NULL);
static DEVICE_ATTR(lux, 0444, light_raw_data_show, NULL);
static DEVICE_ATTR(raw_data, 0444, light_raw_data_show, NULL);
static DEVICE_ATTR(dhr_sensor_info, 0444, light_get_dhr_sensor_info_show, NULL);
static struct device_attribute *light_attrs[] = {
&dev_attr_vendor,
&dev_attr_name,
&dev_attr_lux,
&dev_attr_raw_data,
&dev_attr_dhr_sensor_info,
NULL,
};
static int __init veml3328_light_factory_init(void)
{
adsp_factory_register(MSG_LIGHT, light_attrs);
pr_info("[FACTORY] %s\n", __func__);
return 0;
}
static void __exit veml3328_light_factory_exit(void)
{
adsp_factory_unregister(MSG_LIGHT);
pr_info("[FACTORY] %s\n", __func__);
}
module_init(veml3328_light_factory_init);
module_exit(veml3328_light_factory_exit);

View File

@@ -0,0 +1,607 @@
/*
* Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 "adsp.h"
#define VENDOR "CAPELLA"
#define CHIP_ID "VCNL36658"
#define PROX_AVG_COUNT 40
#define PROX_ALERT_THRESHOLD 200
#define PROX_TH_READ 0
#define PROX_TH_WRITE 1
#define BUFFER_MAX 128
#define PROX_REG_START 0x80
#define PROX_DETECT_HIGH_TH 16368
#define PROX_DETECT_LOW_TH 1000
extern unsigned int system_rev;
struct vcnl36863_prox_data {
struct hrtimer prox_timer;
struct work_struct work_prox;
struct workqueue_struct *prox_wq;
int min;
int max;
int avg;
int val;
int offset;
int reg_backup[2];
short avgwork_check;
short avgtimer_enabled;
short bBarcodeEnabled;
};
enum {
PRX_THRESHOLD_DETECT_H,
PRX_THRESHOLD_HIGH_DETECT_L,
PRX_THRESHOLD_HIGH_DETECT_H,
PRX_THRESHOLD_RELEASE_L,
};
static struct vcnl36863_prox_data *pdata;
static ssize_t prox_vendor_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR);
}
static ssize_t prox_name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID);
}
static ssize_t prox_raw_data_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
if (pdata->avgwork_check == 0)
get_prox_raw_data(&pdata->val, &pdata->offset);
return snprintf(buf, PAGE_SIZE, "%d\n", pdata->val);
}
static ssize_t prox_avg_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", pdata->min,
pdata->avg, pdata->max);
}
static ssize_t prox_avg_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int new_value;
if (sysfs_streq(buf, "0"))
new_value = 0;
else
new_value = 1;
if (new_value == pdata->avgtimer_enabled)
return size;
if (new_value == 0) {
pdata->avgtimer_enabled = 0;
hrtimer_cancel(&pdata->prox_timer);
cancel_work_sync(&pdata->work_prox);
} else {
pdata->avgtimer_enabled = 1;
hrtimer_start(&pdata->prox_timer,
ns_to_ktime(2000 * NSEC_PER_MSEC),
HRTIMER_MODE_REL);
}
return size;
}
static void prox_work_func(struct work_struct *work)
{
int min = 0, max = 0, avg = 0;
int i;
pdata->avgwork_check = 1;
for (i = 0; i < PROX_AVG_COUNT; i++) {
msleep(20);
get_prox_raw_data(&pdata->val, &pdata->offset);
avg += pdata->val;
if (!i)
min = pdata->val;
else if (pdata->val < min)
min = pdata->val;
if (pdata->val > max)
max = pdata->val;
}
avg /= PROX_AVG_COUNT;
pdata->min = min;
pdata->avg = avg;
pdata->max = max;
pdata->avgwork_check = 0;
}
static enum hrtimer_restart prox_timer_func(struct hrtimer *timer)
{
queue_work(pdata->prox_wq, &pdata->work_prox);
hrtimer_forward_now(&pdata->prox_timer,
ns_to_ktime(2000 * NSEC_PER_MSEC));
return HRTIMER_RESTART;
}
int get_prox_threshold(struct adsp_data *data, int type)
{
uint8_t cnt = 0;
int32_t msg_buf[2];
int ret = 0;
msg_buf[0] = type;
msg_buf[1] = 0;
mutex_lock(&data->prox_factory_mutex);
adsp_unicast(msg_buf, sizeof(msg_buf),
MSG_PROX, 0, MSG_TYPE_GET_THRESHOLD);
while (!(data->ready_flag[MSG_TYPE_GET_THRESHOLD] & 1 << MSG_PROX) &&
cnt++ < TIMEOUT_CNT)
usleep_range(2000, 2050);
data->ready_flag[MSG_TYPE_GET_THRESHOLD] &= ~(1 << MSG_PROX);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
mutex_unlock(&data->prox_factory_mutex);
return ret;
}
ret = data->msg_buf[MSG_PROX][0];
mutex_unlock(&data->prox_factory_mutex);
return ret;
}
void set_prox_threshold(struct adsp_data *data, int type, int val)
{
uint8_t cnt = 0;
int32_t msg_buf[2];
msg_buf[0] = type;
msg_buf[1] = val;
mutex_lock(&data->prox_factory_mutex);
adsp_unicast(msg_buf, sizeof(msg_buf),
MSG_PROX, 0, MSG_TYPE_SET_THRESHOLD);
while (!(data->ready_flag[MSG_TYPE_SET_THRESHOLD] & 1 << MSG_PROX) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_SET_THRESHOLD] &= ~(1 << MSG_PROX);
if (cnt >= TIMEOUT_CNT)
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
mutex_unlock(&data->prox_factory_mutex);
}
static ssize_t prox_cancel_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
int hi_thd, low_thd;
hi_thd = get_prox_threshold(data, PRX_THRESHOLD_DETECT_H);
low_thd = get_prox_threshold(data, PRX_THRESHOLD_RELEASE_L);
if (pdata->avgwork_check == 0)
get_prox_raw_data(&pdata->val, &pdata->offset);
pr_info("[FACTORY] %s: offset: %d, hi thd: %d, lo thd: %d\n", __func__,
pdata->offset, hi_thd, low_thd);
return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n",
pdata->offset, hi_thd, low_thd);
}
static ssize_t prox_cancel_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
pr_info("[FACTORY] %s: skip\n", __func__);
return size;
}
static ssize_t prox_thresh_high_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
int thd;
thd = get_prox_threshold(data, PRX_THRESHOLD_DETECT_H);
pr_info("[FACTORY] %s: %d\n", __func__, thd);
return snprintf(buf, PAGE_SIZE, "%d\n", thd);
}
static ssize_t prox_thresh_high_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct adsp_data *data = dev_get_drvdata(dev);
int thd = 0;
if (kstrtoint(buf, 10, &thd)) {
pr_err("[FACTORY] %s: kstrtoint fail\n", __func__);
return size;
}
set_prox_threshold(data, PRX_THRESHOLD_DETECT_H, thd);
pr_info("[FACTORY] %s: %d\n", __func__, thd);
return size;
}
static ssize_t prox_thresh_low_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
int thd;
thd = get_prox_threshold(data, PRX_THRESHOLD_RELEASE_L);
pr_info("[FACTORY] %s: %d\n", __func__, thd);
return snprintf(buf, PAGE_SIZE, "%d\n", thd);
}
static ssize_t prox_thresh_low_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct adsp_data *data = dev_get_drvdata(dev);
int thd = 0;
if (kstrtoint(buf, 10, &thd)) {
pr_err("[FACTORY] %s: kstrtoint fail\n", __func__);
return size;
}
set_prox_threshold(data, PRX_THRESHOLD_RELEASE_L, thd);
pr_info("[FACTORY] %s: %d\n", __func__, thd);
return size;
}
#ifdef CONFIG_SUPPORT_PROX_AUTO_CAL
static ssize_t prox_high_thresh_high_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
int thd;
thd = get_prox_threshold(data, PRX_THRESHOLD_HIGH_DETECT_H);
pr_info("[FACTORY] %s: %d\n", __func__, thd);
return snprintf(buf, PAGE_SIZE, "%d\n", thd);
}
static ssize_t prox_high_thresh_high_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct adsp_data *data = dev_get_drvdata(dev);
int thd = 0;
if (kstrtoint(buf, 10, &thd)) {
pr_err("[FACTORY] %s: kstrtoint fail\n", __func__);
return size;
}
set_prox_threshold(data, PRX_THRESHOLD_HIGH_DETECT_H, thd);
pr_info("[FACTORY] %s: %d\n", __func__, thd);
return size;
}
static ssize_t prox_high_thresh_low_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
int thd;
thd = get_prox_threshold(data, PRX_THRESHOLD_HIGH_DETECT_L);
pr_info("[FACTORY] %s: %d\n", __func__, thd);
return snprintf(buf, PAGE_SIZE, "%d\n", thd);
}
static ssize_t prox_high_thresh_low_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct adsp_data *data = dev_get_drvdata(dev);
int thd = 0;
if (kstrtoint(buf, 10, &thd)) {
pr_err("[FACTORY] %s: kstrtoint fail\n", __func__);
return size;
}
set_prox_threshold(data, PRX_THRESHOLD_HIGH_DETECT_L, thd);
pr_info("[FACTORY] %s: %d\n", __func__, thd);
return size;
}
#endif
static ssize_t barcode_emul_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%u\n", pdata->bBarcodeEnabled);
}
static ssize_t barcode_emul_enable_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int iRet;
int64_t dEnable;
iRet = kstrtoll(buf, 10, &dEnable);
if (iRet < 0)
return iRet;
if (dEnable)
pdata->bBarcodeEnabled = 1;
else
pdata->bBarcodeEnabled = 0;
return size;
}
static ssize_t prox_cancel_pass_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "1\n");
}
static ssize_t prox_default_trim_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", pdata->offset);
}
static ssize_t prox_alert_thresh_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", PROX_ALERT_THRESHOLD);
}
static ssize_t prox_register_read_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
int cnt = 0;
int32_t msg_buf[1];
msg_buf[0] = pdata->reg_backup[0];
mutex_lock(&data->prox_factory_mutex);
adsp_unicast(msg_buf, sizeof(msg_buf),
MSG_PROX, 0, MSG_TYPE_GET_REGISTER);
while (!(data->ready_flag[MSG_TYPE_GET_REGISTER] & 1 << MSG_PROX) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_REGISTER] &= ~(1 << MSG_PROX);
if (cnt >= TIMEOUT_CNT)
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
pdata->reg_backup[1] = data->msg_buf[MSG_PROX][0];
pr_info("[FACTORY] %s: [0x%x]: 0x%x\n",
__func__, pdata->reg_backup[0], pdata->reg_backup[1]);
mutex_unlock(&data->prox_factory_mutex);
return snprintf(buf, PAGE_SIZE, "[0x%x]: 0x%x\n",
pdata->reg_backup[0], pdata->reg_backup[1]);
}
static ssize_t prox_register_read_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int reg = 0;
if (sscanf(buf, "%4x", &reg) != 1) {
pr_err("[FACTORY]: %s - The number of data are wrong\n",
__func__);
return -EINVAL;
}
pdata->reg_backup[0] = reg;
pr_info("[FACTORY] %s: [0x%x]\n", __func__, pdata->reg_backup[0]);
return size;
}
static ssize_t prox_register_write_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct adsp_data *data = dev_get_drvdata(dev);
int cnt = 0;
int32_t msg_buf[2];
if (sscanf(buf, "%2x,%4x", &msg_buf[0], &msg_buf[1]) != 2) {
pr_err("[FACTORY]: %s - The number of data are wrong\n",
__func__);
return -EINVAL;
}
mutex_lock(&data->prox_factory_mutex);
adsp_unicast(msg_buf, sizeof(msg_buf),
MSG_PROX, 0, MSG_TYPE_SET_REGISTER);
while (!(data->ready_flag[MSG_TYPE_SET_REGISTER] & 1 << MSG_PROX) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_SET_REGISTER] &= ~(1 << MSG_PROX);
if (cnt >= TIMEOUT_CNT)
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
pdata->reg_backup[0] = msg_buf[0];
pr_info("[FACTORY] %s: 0x%x - 0x%x\n",
__func__, msg_buf[0], data->msg_buf[MSG_PROX][0]);
mutex_unlock(&data->prox_factory_mutex);
return size;
}
static ssize_t prox_light_get_dhr_sensor_info_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
int offset = 0;
int32_t *info = data->msg_buf[MSG_PROX];
mutex_lock(&data->prox_factory_mutex);
adsp_unicast(NULL, 0, MSG_PROX, 0, MSG_TYPE_GET_DHR_INFO);
while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << MSG_PROX) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << MSG_PROX);
if (cnt >= TIMEOUT_CNT)
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
pr_info("[FACTORY] %d,%d,%d,%d,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%d\n",
info[0], info[1], info[2], info[3], info[4], info[5],
info[6], info[7], info[8], info[9], info[10], info[11]);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"\"THD\":\"%d %d %d %d\",", info[0], info[1], info[2], info[3]);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"\"PDRIVE_CURRENT\":\"%02x\",", info[4]);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"\"PERSIST_TIME\":\"%02x\",", info[5]);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"\"PPULSE\":\"%02x\",", info[6]);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"\"PGAIN\":\"%02x\",", info[7]);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"\"PTIME\":\"%02x\",", info[8]);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"\"PPLUSE_LEN\":\"%02x\",", info[9]);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"\"ATIME\":\"%02x\",", info[10]);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"\"POFFSET\":\"%d\"\n", info[11]);
mutex_unlock(&data->prox_factory_mutex);
return offset;
}
static DEVICE_ATTR(vendor, 0444, prox_vendor_show, NULL);
static DEVICE_ATTR(name, 0444, prox_name_show, NULL);
static DEVICE_ATTR(state, 0444, prox_raw_data_show, NULL);
static DEVICE_ATTR(raw_data, 0444, prox_raw_data_show, NULL);
static DEVICE_ATTR(prox_avg, 0664,
prox_avg_show, prox_avg_store);
static DEVICE_ATTR(prox_cal, 0664,
prox_cancel_show, prox_cancel_store);
static DEVICE_ATTR(thresh_high, 0664,
prox_thresh_high_show, prox_thresh_high_store);
static DEVICE_ATTR(thresh_low, 0664,
prox_thresh_low_show, prox_thresh_low_store);
#ifdef CONFIG_SUPPORT_PROX_AUTO_CAL
static DEVICE_ATTR(high_thresh_high, 0664,
prox_high_thresh_high_show, prox_high_thresh_high_store);
static DEVICE_ATTR(high_thresh_low, 0664,
prox_high_thresh_low_show, prox_high_thresh_low_store);
#endif
static DEVICE_ATTR(register_write, 0220,
NULL, prox_register_write_store);
static DEVICE_ATTR(register_read, 0664,
prox_register_read_show, prox_register_read_store);
static DEVICE_ATTR(barcode_emul_en, 0664,
barcode_emul_enable_show, barcode_emul_enable_store);
static DEVICE_ATTR(prox_offset_pass, 0444, prox_cancel_pass_show, NULL);
static DEVICE_ATTR(prox_trim, 0444, prox_default_trim_show, NULL);
static DEVICE_ATTR(prox_alert_thresh, 0444, prox_alert_thresh_show, NULL);
static DEVICE_ATTR(dhr_sensor_info, 0440,
prox_light_get_dhr_sensor_info_show, NULL);
static struct device_attribute *prox_attrs[] = {
&dev_attr_vendor,
&dev_attr_name,
&dev_attr_state,
&dev_attr_raw_data,
&dev_attr_prox_avg,
&dev_attr_prox_cal,
&dev_attr_thresh_high,
&dev_attr_thresh_low,
#ifdef CONFIG_SUPPORT_PROX_AUTO_CAL
&dev_attr_high_thresh_high,
&dev_attr_high_thresh_low,
#endif
&dev_attr_barcode_emul_en,
&dev_attr_prox_offset_pass,
&dev_attr_prox_trim,
&dev_attr_prox_alert_thresh,
&dev_attr_dhr_sensor_info,
&dev_attr_register_write,
&dev_attr_register_read,
NULL,
};
static int __init vcnl36863_prox_factory_init(void)
{
pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
adsp_factory_register(MSG_PROX, prox_attrs);
pr_info("[FACTORY] %s\n", __func__);
hrtimer_init(&pdata->prox_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
pdata->prox_timer.function = prox_timer_func;
pdata->prox_wq = create_singlethread_workqueue("prox_wq");
/* this is the thread function we run on the work queue */
INIT_WORK(&pdata->work_prox, prox_work_func);
pdata->avgwork_check = 0;
pdata->avgtimer_enabled = 0;
pdata->avg = 0;
pdata->min = 0;
pdata->max = 0;
pdata->offset = 0;
return 0;
}
static void __exit vcnl36863_prox_factory_exit(void)
{
if (pdata->avgtimer_enabled == 1) {
hrtimer_cancel(&pdata->prox_timer);
cancel_work_sync(&pdata->work_prox);
}
destroy_workqueue(pdata->prox_wq);
adsp_factory_unregister(MSG_PROX);
kfree(pdata);
pr_info("[FACTORY] %s\n", __func__);
}
module_init(vcnl36863_prox_factory_init);
module_exit(vcnl36863_prox_factory_exit);

View File

@@ -0,0 +1,118 @@
/*
* Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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/dirent.h>
#include "adsp.h"
#define VENDOR "CAPELLA"
#define CHIP_ID "VCNL36863"
/*************************************************************************/
/* factory Sysfs */
/*************************************************************************/
static ssize_t light_vendor_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR);
}
static ssize_t light_name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID);
}
static ssize_t light_raw_data_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
mutex_lock(&data->light_factory_mutex);
adsp_unicast(NULL, 0, MSG_LIGHT, 0, MSG_TYPE_GET_RAW_DATA);
while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_LIGHT) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_LIGHT);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
mutex_unlock(&data->light_factory_mutex);
return snprintf(buf, PAGE_SIZE, "0,0,0,0,0,0\n");
}
mutex_unlock(&data->light_factory_mutex);
return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d\n",
data->msg_buf[MSG_LIGHT][0], data->msg_buf[MSG_LIGHT][1],
data->msg_buf[MSG_LIGHT][2], data->msg_buf[MSG_LIGHT][3],
data->msg_buf[MSG_LIGHT][4], data->msg_buf[MSG_LIGHT][5]);
}
static ssize_t light_get_dhr_sensor_info_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
pr_info("[FACTORY] %s: start\n", __func__);
mutex_lock(&data->light_factory_mutex);
adsp_unicast(NULL, 0, MSG_LIGHT, 0, MSG_TYPE_GET_DHR_INFO);
while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << MSG_LIGHT) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << MSG_LIGHT);
if (cnt >= TIMEOUT_CNT)
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
mutex_unlock(&data->light_factory_mutex);
return data->msg_buf[MSG_LIGHT][0];
}
static DEVICE_ATTR(vendor, 0444, light_vendor_show, NULL);
static DEVICE_ATTR(name, 0444, light_name_show, NULL);
static DEVICE_ATTR(lux, 0444, light_raw_data_show, NULL);
static DEVICE_ATTR(raw_data, 0444, light_raw_data_show, NULL);
static DEVICE_ATTR(dhr_sensor_info, 0444, light_get_dhr_sensor_info_show, NULL);
static struct device_attribute *light_attrs[] = {
&dev_attr_vendor,
&dev_attr_name,
&dev_attr_lux,
&dev_attr_raw_data,
&dev_attr_dhr_sensor_info,
NULL,
};
static int __init vcnl36863_light_factory_init(void)
{
adsp_factory_register(MSG_LIGHT, light_attrs);
pr_info("[FACTORY] %s\n", __func__);
return 0;
}
static void __exit vcnl36863_light_factory_exit(void)
{
adsp_factory_unregister(MSG_LIGHT);
pr_info("[FACTORY] %s\n", __func__);
}
module_init(vcnl36863_light_factory_init);
module_exit(vcnl36863_light_factory_exit);

View File

@@ -0,0 +1,607 @@
/*
* Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 "adsp.h"
#define VENDOR "CAPELLA"
#define CHIP_ID "VCNL36863"
#define PROX_AVG_COUNT 40
#define PROX_ALERT_THRESHOLD 200
#define PROX_TH_READ 0
#define PROX_TH_WRITE 1
#define BUFFER_MAX 128
#define PROX_REG_START 0x80
#define PROX_DETECT_HIGH_TH 16368
#define PROX_DETECT_LOW_TH 1000
extern unsigned int system_rev;
struct vcnl36863_prox_data {
struct hrtimer prox_timer;
struct work_struct work_prox;
struct workqueue_struct *prox_wq;
int min;
int max;
int avg;
int val;
int offset;
int reg_backup[2];
short avgwork_check;
short avgtimer_enabled;
short bBarcodeEnabled;
};
enum {
PRX_THRESHOLD_DETECT_H,
PRX_THRESHOLD_HIGH_DETECT_L,
PRX_THRESHOLD_HIGH_DETECT_H,
PRX_THRESHOLD_RELEASE_L,
};
static struct vcnl36863_prox_data *pdata;
static ssize_t prox_vendor_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR);
}
static ssize_t prox_name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID);
}
static ssize_t prox_raw_data_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
if (pdata->avgwork_check == 0)
get_prox_raw_data(&pdata->val, &pdata->offset);
return snprintf(buf, PAGE_SIZE, "%d\n", pdata->val);
}
static ssize_t prox_avg_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n", pdata->min,
pdata->avg, pdata->max);
}
static ssize_t prox_avg_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int new_value;
if (sysfs_streq(buf, "0"))
new_value = 0;
else
new_value = 1;
if (new_value == pdata->avgtimer_enabled)
return size;
if (new_value == 0) {
pdata->avgtimer_enabled = 0;
hrtimer_cancel(&pdata->prox_timer);
cancel_work_sync(&pdata->work_prox);
} else {
pdata->avgtimer_enabled = 1;
hrtimer_start(&pdata->prox_timer,
ns_to_ktime(2000 * NSEC_PER_MSEC),
HRTIMER_MODE_REL);
}
return size;
}
static void prox_work_func(struct work_struct *work)
{
int min = 0, max = 0, avg = 0;
int i;
pdata->avgwork_check = 1;
for (i = 0; i < PROX_AVG_COUNT; i++) {
msleep(20);
get_prox_raw_data(&pdata->val, &pdata->offset);
avg += pdata->val;
if (!i)
min = pdata->val;
else if (pdata->val < min)
min = pdata->val;
if (pdata->val > max)
max = pdata->val;
}
avg /= PROX_AVG_COUNT;
pdata->min = min;
pdata->avg = avg;
pdata->max = max;
pdata->avgwork_check = 0;
}
static enum hrtimer_restart prox_timer_func(struct hrtimer *timer)
{
queue_work(pdata->prox_wq, &pdata->work_prox);
hrtimer_forward_now(&pdata->prox_timer,
ns_to_ktime(2000 * NSEC_PER_MSEC));
return HRTIMER_RESTART;
}
int get_prox_threshold(struct adsp_data *data, int type)
{
uint8_t cnt = 0;
int32_t msg_buf[2];
int ret = 0;
msg_buf[0] = type;
msg_buf[1] = 0;
mutex_lock(&data->prox_factory_mutex);
adsp_unicast(msg_buf, sizeof(msg_buf),
MSG_PROX, 0, MSG_TYPE_GET_THRESHOLD);
while (!(data->ready_flag[MSG_TYPE_GET_THRESHOLD] & 1 << MSG_PROX) &&
cnt++ < TIMEOUT_CNT)
usleep_range(2000, 2050);
data->ready_flag[MSG_TYPE_GET_THRESHOLD] &= ~(1 << MSG_PROX);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
mutex_unlock(&data->prox_factory_mutex);
return ret;
}
ret = data->msg_buf[MSG_PROX][0];
mutex_unlock(&data->prox_factory_mutex);
return ret;
}
void set_prox_threshold(struct adsp_data *data, int type, int val)
{
uint8_t cnt = 0;
int32_t msg_buf[2];
msg_buf[0] = type;
msg_buf[1] = val;
mutex_lock(&data->prox_factory_mutex);
adsp_unicast(msg_buf, sizeof(msg_buf),
MSG_PROX, 0, MSG_TYPE_SET_THRESHOLD);
while (!(data->ready_flag[MSG_TYPE_SET_THRESHOLD] & 1 << MSG_PROX) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_SET_THRESHOLD] &= ~(1 << MSG_PROX);
if (cnt >= TIMEOUT_CNT)
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
mutex_unlock(&data->prox_factory_mutex);
}
static ssize_t prox_cancel_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
int hi_thd, low_thd;
hi_thd = get_prox_threshold(data, PRX_THRESHOLD_DETECT_H);
low_thd = get_prox_threshold(data, PRX_THRESHOLD_RELEASE_L);
if (pdata->avgwork_check == 0)
get_prox_raw_data(&pdata->val, &pdata->offset);
pr_info("[FACTORY] %s: offset: %d, hi thd: %d, lo thd: %d\n", __func__,
pdata->offset, hi_thd, low_thd);
return snprintf(buf, PAGE_SIZE, "%d,%d,%d\n",
pdata->offset, hi_thd, low_thd);
}
static ssize_t prox_cancel_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
pr_info("[FACTORY] %s: skip\n", __func__);
return size;
}
static ssize_t prox_thresh_high_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
int thd;
thd = get_prox_threshold(data, PRX_THRESHOLD_DETECT_H);
pr_info("[FACTORY] %s: %d\n", __func__, thd);
return snprintf(buf, PAGE_SIZE, "%d\n", thd);
}
static ssize_t prox_thresh_high_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct adsp_data *data = dev_get_drvdata(dev);
int thd = 0;
if (kstrtoint(buf, 10, &thd)) {
pr_err("[FACTORY] %s: kstrtoint fail\n", __func__);
return size;
}
set_prox_threshold(data, PRX_THRESHOLD_DETECT_H, thd);
pr_info("[FACTORY] %s: %d\n", __func__, thd);
return size;
}
static ssize_t prox_thresh_low_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
int thd;
thd = get_prox_threshold(data, PRX_THRESHOLD_RELEASE_L);
pr_info("[FACTORY] %s: %d\n", __func__, thd);
return snprintf(buf, PAGE_SIZE, "%d\n", thd);
}
static ssize_t prox_thresh_low_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct adsp_data *data = dev_get_drvdata(dev);
int thd = 0;
if (kstrtoint(buf, 10, &thd)) {
pr_err("[FACTORY] %s: kstrtoint fail\n", __func__);
return size;
}
set_prox_threshold(data, PRX_THRESHOLD_RELEASE_L, thd);
pr_info("[FACTORY] %s: %d\n", __func__, thd);
return size;
}
#ifdef CONFIG_SUPPORT_PROX_AUTO_CAL
static ssize_t prox_high_thresh_high_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
int thd;
thd = get_prox_threshold(data, PRX_THRESHOLD_HIGH_DETECT_H);
pr_info("[FACTORY] %s: %d\n", __func__, thd);
return snprintf(buf, PAGE_SIZE, "%d\n", thd);
}
static ssize_t prox_high_thresh_high_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct adsp_data *data = dev_get_drvdata(dev);
int thd = 0;
if (kstrtoint(buf, 10, &thd)) {
pr_err("[FACTORY] %s: kstrtoint fail\n", __func__);
return size;
}
set_prox_threshold(data, PRX_THRESHOLD_HIGH_DETECT_H, thd);
pr_info("[FACTORY] %s: %d\n", __func__, thd);
return size;
}
static ssize_t prox_high_thresh_low_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
int thd;
thd = get_prox_threshold(data, PRX_THRESHOLD_HIGH_DETECT_L);
pr_info("[FACTORY] %s: %d\n", __func__, thd);
return snprintf(buf, PAGE_SIZE, "%d\n", thd);
}
static ssize_t prox_high_thresh_low_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct adsp_data *data = dev_get_drvdata(dev);
int thd = 0;
if (kstrtoint(buf, 10, &thd)) {
pr_err("[FACTORY] %s: kstrtoint fail\n", __func__);
return size;
}
set_prox_threshold(data, PRX_THRESHOLD_HIGH_DETECT_L, thd);
pr_info("[FACTORY] %s: %d\n", __func__, thd);
return size;
}
#endif
static ssize_t barcode_emul_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%u\n", pdata->bBarcodeEnabled);
}
static ssize_t barcode_emul_enable_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int iRet;
int64_t dEnable;
iRet = kstrtoll(buf, 10, &dEnable);
if (iRet < 0)
return iRet;
if (dEnable)
pdata->bBarcodeEnabled = 1;
else
pdata->bBarcodeEnabled = 0;
return size;
}
static ssize_t prox_cancel_pass_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "1\n");
}
static ssize_t prox_default_trim_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", pdata->offset);
}
static ssize_t prox_alert_thresh_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%d\n", PROX_ALERT_THRESHOLD);
}
static ssize_t prox_register_read_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
int cnt = 0;
int32_t msg_buf[1];
msg_buf[0] = pdata->reg_backup[0];
mutex_lock(&data->prox_factory_mutex);
adsp_unicast(msg_buf, sizeof(msg_buf),
MSG_PROX, 0, MSG_TYPE_GET_REGISTER);
while (!(data->ready_flag[MSG_TYPE_GET_REGISTER] & 1 << MSG_PROX) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_REGISTER] &= ~(1 << MSG_PROX);
if (cnt >= TIMEOUT_CNT)
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
pdata->reg_backup[1] = data->msg_buf[MSG_PROX][0];
pr_info("[FACTORY] %s: [0x%x]: 0x%x\n",
__func__, pdata->reg_backup[0], pdata->reg_backup[1]);
mutex_unlock(&data->prox_factory_mutex);
return snprintf(buf, PAGE_SIZE, "[0x%x]: 0x%x\n",
pdata->reg_backup[0], pdata->reg_backup[1]);
}
static ssize_t prox_register_read_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int reg = 0;
if (sscanf(buf, "%4x", &reg) != 1) {
pr_err("[FACTORY]: %s - The number of data are wrong\n",
__func__);
return -EINVAL;
}
pdata->reg_backup[0] = reg;
pr_info("[FACTORY] %s: [0x%x]\n", __func__, pdata->reg_backup[0]);
return size;
}
static ssize_t prox_register_write_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct adsp_data *data = dev_get_drvdata(dev);
int cnt = 0;
int32_t msg_buf[2];
if (sscanf(buf, "%2x,%4x", &msg_buf[0], &msg_buf[1]) != 2) {
pr_err("[FACTORY]: %s - The number of data are wrong\n",
__func__);
return -EINVAL;
}
mutex_lock(&data->prox_factory_mutex);
adsp_unicast(msg_buf, sizeof(msg_buf),
MSG_PROX, 0, MSG_TYPE_SET_REGISTER);
while (!(data->ready_flag[MSG_TYPE_SET_REGISTER] & 1 << MSG_PROX) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_SET_REGISTER] &= ~(1 << MSG_PROX);
if (cnt >= TIMEOUT_CNT)
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
pdata->reg_backup[0] = msg_buf[0];
pr_info("[FACTORY] %s: 0x%x - 0x%x\n",
__func__, msg_buf[0], data->msg_buf[MSG_PROX][0]);
mutex_unlock(&data->prox_factory_mutex);
return size;
}
static ssize_t prox_light_get_dhr_sensor_info_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
int offset = 0;
int32_t *info = data->msg_buf[MSG_PROX];
mutex_lock(&data->prox_factory_mutex);
adsp_unicast(NULL, 0, MSG_PROX, 0, MSG_TYPE_GET_DHR_INFO);
while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << MSG_PROX) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << MSG_PROX);
if (cnt >= TIMEOUT_CNT)
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
pr_info("[FACTORY] %d,%d,%d,%d,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%d\n",
info[0], info[1], info[2], info[3], info[4], info[5],
info[6], info[7], info[8], info[9], info[10], info[11]);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"\"THD\":\"%d %d %d %d\",", info[0], info[1], info[2], info[3]);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"\"PDRIVE_CURRENT\":\"%02x\",", info[4]);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"\"PERSIST_TIME\":\"%02x\",", info[5]);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"\"PPULSE\":\"%02x\",", info[6]);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"\"PGAIN\":\"%02x\",", info[7]);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"\"PTIME\":\"%02x\",", info[8]);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"\"PPLUSE_LEN\":\"%02x\",", info[9]);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"\"ATIME\":\"%02x\",", info[10]);
offset += snprintf(buf + offset, PAGE_SIZE - offset,
"\"POFFSET\":\"%d\"\n", info[11]);
mutex_unlock(&data->prox_factory_mutex);
return offset;
}
static DEVICE_ATTR(vendor, 0444, prox_vendor_show, NULL);
static DEVICE_ATTR(name, 0444, prox_name_show, NULL);
static DEVICE_ATTR(state, 0444, prox_raw_data_show, NULL);
static DEVICE_ATTR(raw_data, 0444, prox_raw_data_show, NULL);
static DEVICE_ATTR(prox_avg, 0664,
prox_avg_show, prox_avg_store);
static DEVICE_ATTR(prox_cal, 0664,
prox_cancel_show, prox_cancel_store);
static DEVICE_ATTR(thresh_high, 0664,
prox_thresh_high_show, prox_thresh_high_store);
static DEVICE_ATTR(thresh_low, 0664,
prox_thresh_low_show, prox_thresh_low_store);
#ifdef CONFIG_SUPPORT_PROX_AUTO_CAL
static DEVICE_ATTR(high_thresh_high, 0664,
prox_high_thresh_high_show, prox_high_thresh_high_store);
static DEVICE_ATTR(high_thresh_low, 0664,
prox_high_thresh_low_show, prox_high_thresh_low_store);
#endif
static DEVICE_ATTR(register_write, 0220,
NULL, prox_register_write_store);
static DEVICE_ATTR(register_read, 0664,
prox_register_read_show, prox_register_read_store);
static DEVICE_ATTR(barcode_emul_en, 0664,
barcode_emul_enable_show, barcode_emul_enable_store);
static DEVICE_ATTR(prox_offset_pass, 0444, prox_cancel_pass_show, NULL);
static DEVICE_ATTR(prox_trim, 0444, prox_default_trim_show, NULL);
static DEVICE_ATTR(prox_alert_thresh, 0444, prox_alert_thresh_show, NULL);
static DEVICE_ATTR(dhr_sensor_info, 0440,
prox_light_get_dhr_sensor_info_show, NULL);
static struct device_attribute *prox_attrs[] = {
&dev_attr_vendor,
&dev_attr_name,
&dev_attr_state,
&dev_attr_raw_data,
&dev_attr_prox_avg,
&dev_attr_prox_cal,
&dev_attr_thresh_high,
&dev_attr_thresh_low,
#ifdef CONFIG_SUPPORT_PROX_AUTO_CAL
&dev_attr_high_thresh_high,
&dev_attr_high_thresh_low,
#endif
&dev_attr_barcode_emul_en,
&dev_attr_prox_offset_pass,
&dev_attr_prox_trim,
&dev_attr_prox_alert_thresh,
&dev_attr_dhr_sensor_info,
&dev_attr_register_write,
&dev_attr_register_read,
NULL,
};
static int __init vcnl36863_prox_factory_init(void)
{
pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
adsp_factory_register(MSG_PROX, prox_attrs);
pr_info("[FACTORY] %s\n", __func__);
hrtimer_init(&pdata->prox_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
pdata->prox_timer.function = prox_timer_func;
pdata->prox_wq = create_singlethread_workqueue("prox_wq");
/* this is the thread function we run on the work queue */
INIT_WORK(&pdata->work_prox, prox_work_func);
pdata->avgwork_check = 0;
pdata->avgtimer_enabled = 0;
pdata->avg = 0;
pdata->min = 0;
pdata->max = 0;
pdata->offset = 0;
return 0;
}
static void __exit vcnl36863_prox_factory_exit(void)
{
if (pdata->avgtimer_enabled == 1) {
hrtimer_cancel(&pdata->prox_timer);
cancel_work_sync(&pdata->work_prox);
}
destroy_workqueue(pdata->prox_wq);
adsp_factory_unregister(MSG_PROX);
kfree(pdata);
pr_info("[FACTORY] %s\n", __func__);
}
module_init(vcnl36863_prox_factory_init);
module_exit(vcnl36863_prox_factory_exit);

View File

@@ -0,0 +1,118 @@
/*
* Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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/dirent.h>
#include "adsp.h"
#define VENDOR "CAPELLA"
#define CHIP_ID "VEML3328"
/*************************************************************************/
/* factory Sysfs */
/*************************************************************************/
static ssize_t light_vendor_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", VENDOR);
}
static ssize_t light_name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", CHIP_ID);
}
static ssize_t light_raw_data_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
mutex_lock(&data->light_factory_mutex);
adsp_unicast(NULL, 0, MSG_LIGHT, 0, MSG_TYPE_GET_RAW_DATA);
while (!(data->ready_flag[MSG_TYPE_GET_RAW_DATA] & 1 << MSG_LIGHT) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_RAW_DATA] &= ~(1 << MSG_LIGHT);
if (cnt >= TIMEOUT_CNT) {
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
mutex_unlock(&data->light_factory_mutex);
return snprintf(buf, PAGE_SIZE, "0,0,0,0,0,0\n");
}
mutex_unlock(&data->light_factory_mutex);
return snprintf(buf, PAGE_SIZE, "%d,%d,%d,%d,%d,%d\n",
data->msg_buf[MSG_LIGHT][0], data->msg_buf[MSG_LIGHT][1],
data->msg_buf[MSG_LIGHT][2], data->msg_buf[MSG_LIGHT][3],
data->msg_buf[MSG_LIGHT][4], data->msg_buf[MSG_LIGHT][5]);
}
static ssize_t light_get_dhr_sensor_info_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adsp_data *data = dev_get_drvdata(dev);
uint8_t cnt = 0;
pr_info("[FACTORY] %s: start\n", __func__);
mutex_lock(&data->light_factory_mutex);
adsp_unicast(NULL, 0, MSG_LIGHT, 0, MSG_TYPE_GET_DHR_INFO);
while (!(data->ready_flag[MSG_TYPE_GET_DHR_INFO] & 1 << MSG_LIGHT) &&
cnt++ < TIMEOUT_CNT)
usleep_range(500, 550);
data->ready_flag[MSG_TYPE_GET_DHR_INFO] &= ~(1 << MSG_LIGHT);
if (cnt >= TIMEOUT_CNT)
pr_err("[FACTORY] %s: Timeout!!!\n", __func__);
mutex_unlock(&data->light_factory_mutex);
return data->msg_buf[MSG_LIGHT][0];
}
static DEVICE_ATTR(vendor, 0444, light_vendor_show, NULL);
static DEVICE_ATTR(name, 0444, light_name_show, NULL);
static DEVICE_ATTR(lux, 0444, light_raw_data_show, NULL);
static DEVICE_ATTR(raw_data, 0444, light_raw_data_show, NULL);
static DEVICE_ATTR(dhr_sensor_info, 0444, light_get_dhr_sensor_info_show, NULL);
static struct device_attribute *light_attrs[] = {
&dev_attr_vendor,
&dev_attr_name,
&dev_attr_lux,
&dev_attr_raw_data,
&dev_attr_dhr_sensor_info,
NULL,
};
static int __init veml3328_light_factory_init(void)
{
adsp_factory_register(MSG_LIGHT, light_attrs);
pr_info("[FACTORY] %s\n", __func__);
return 0;
}
static void __exit veml3328_light_factory_exit(void)
{
adsp_factory_unregister(MSG_LIGHT);
pr_info("[FACTORY] %s\n", __func__);
}
module_init(veml3328_light_factory_init);
module_exit(veml3328_light_factory_exit);

View File

@@ -297,7 +297,9 @@ static const char * const fw_path[] = {
"/lib/firmware/updates/" UTS_RELEASE,
"/lib/firmware/updates",
"/lib/firmware/" UTS_RELEASE,
"/lib/firmware"
"/lib/firmware",
"/firmware/image",
"/firmware-modem/image"
};
/*

View File

@@ -21,6 +21,9 @@
#include <linux/irqdesc.h>
#include "power.h"
#ifdef CONFIG_SEC_PM
#include <linux/wakeup_reason.h>
#endif
/*
* If set, the suspend/hibernate code will abort transitions to a sleep state
@@ -663,6 +666,12 @@ static void wakeup_source_deactivate(struct wakeup_source *ws)
ws->total_time = ktime_add(ws->total_time, duration);
if (ktime_to_ns(duration) > ktime_to_ns(ws->max_time))
ws->max_time = duration;
#ifdef CONFIG_SEC_PM_DEBUG
if (ktime_to_ms(duration) >= 10000LL) {
pr_info("PM: unlock %s(%lld ms)\n",
ws->name, ktime_to_ms(duration));
}
#endif
ws->last_time = now;
del_timer(&ws->timer);
@@ -928,8 +937,12 @@ void pm_system_irq_wakeup(unsigned int irq_number)
else if (desc->action && desc->action->name)
name = desc->action->name;
#ifdef CONFIG_SEC_PM
log_wakeup_reason(irq_number);
#else
pr_warn("%s: %d triggered %s\n", __func__,
irq_number, name);
#endif
}
pm_wakeup_irq = irq_number;
@@ -1078,6 +1091,62 @@ static int print_wakeup_source_stats(struct seq_file *m,
return 0;
}
#ifdef CONFIG_SEC_PM_DEBUG
static int print_wakeup_source_active(
struct wakeup_source *ws)
{
unsigned long flags;
ktime_t total_time;
unsigned long active_count;
ktime_t active_time;
ktime_t prevent_sleep_time;
int ret;
spin_lock_irqsave(&ws->lock, flags);
total_time = ws->total_time;
prevent_sleep_time = ws->prevent_sleep_time;
active_count = ws->active_count;
if (ws->active) {
ktime_t now = ktime_get();
active_time = ktime_sub(now, ws->last_time);
total_time = ktime_add(total_time, active_time);
if (ws->autosleep_enabled)
prevent_sleep_time = ktime_add(prevent_sleep_time,
ktime_sub(now, ws->start_prevent_time));
} else {
active_time = ktime_set(0, 0);
}
ret = pr_info("<%s>\tCount(%lu) Time(%lld/%lld) Prevent(%lld)\n",
ws->name, active_count,
ktime_to_ms(active_time), ktime_to_ms(total_time),
ktime_to_ms(prevent_sleep_time));
spin_unlock_irqrestore(&ws->lock, flags);
return ret;
}
int wakeup_sources_stats_active(void)
{
struct wakeup_source *ws;
pr_info("Active wake lock:\n");
rcu_read_lock();
list_for_each_entry_rcu(ws, &wakeup_sources, entry)
if (ws->active)
print_wakeup_source_active(ws);
rcu_read_unlock();
return 0;
}
EXPORT_SYMBOL_GPL(wakeup_sources_stats_active);
#endif
/**
* wakeup_sources_stats_show - Print wakeup sources statistics information.
* @m: seq_file to print the statistics into.

282
drivers/battery_v2/Kconfig Normal file
View File

@@ -0,0 +1,282 @@
config BATTERY_SAMSUNG
tristate "samsung battery driver"
help
Say Y to include support for samsung battery driver
This battery driver integrated all battery-related functions
To see battery-related functions,
refer to sec_charging_common.h
config BATTERY_SAMSUNG_V2
tristate "samsung battery driver version 2"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support for samsung battery driver
This battery driver integrated all battery-related functions
To see battery-related functions,
refer to sec_charging_common.h
config BATTERY_SAMSUNG_DATA_FILE
depends on BATTERY_SAMSUNG
string "samsung battery data file"
default "default_battery_data.h"
help
Path to the battery data file.
config SLOW_CHARGING_CURRENT_STANDARD
int "slow charging"
depends on BATTERY_SAMSUNG
default "1000"
help
Value for standard of slow-charging.
config CHARGING_VZWCONCEPT
tristate "VZW concept about the charging"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support for the VZW concepts.
config BATTERY_SWELLING
bool "prevent battery swelling"
help
Say Y to include support for prevent battery swelling
config BATTERY_SWELLING_SELF_DISCHARGING
bool "prevent battery swelling with self discharging"
help
Say Y to include support for prevent battery swelling with self discharging
config CALC_TIME_TO_FULL
tristate "calculate time to full"
default n
depends on BATTERY_SAMSUNG
help
Say Y to use calc time to full function.
config MULTI_CHARGING
bool "support for multi charger ICs"
help
Say Y to include support for multi charger ICs
config STEP_CHARGING
bool "support for step charging"
help
Say Y to include support for step charging
config UPDATE_BATTERY_DATA
bool "support for updating battery data"
default n
depends on BATTERY_SAMSUNG && OF
help
Say Y to include support for step charging
config AFC_CURR_CONTROL_BY_TEMP
tristate "fast charging current control by temp"
default n
depends on BATTERY_SAMSUNG
help
Say Y to set afc current control by temp
config BATTERY_CISD
bool "support for cisd"
help
Say Y to include support for cisd
cisd means cell internal short detection
config DUMMY_BATTERY
bool "support for dummy battery"
help
Say Y to include support for dummy battery
for dummy battery recognition
# Fuel Gauge
config FUELGAUGE_DUMMY
tristate "dummy fuel gauge driver"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support
for dummy fuel gauge driver.
This driver source code implemented
skeleton source code for fuel gauge functions.
config PREVENT_SOC_JUMP
tristate "prevent soc jump at full-charged"
default n
depends on BATTERY_SAMSUNG
help
Say Y to prevent soc jump
config FUELGAUGE_SM5705
tristate "Siliconmitus SM5705 Fuel Gauge"
default n
depends on I2C
help
SM5705 is fuel-gauge systems for lithium-ion (Li+) batteries
in handheld and portable equipment. The SM5705 is configured
to operate with a single lithium cell
# Charger
config CHARGER_DUMMY
tristate "dummy charger driver"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support
for dummy charger driver.
This driver source code implemented
skeleton source code for charger functions.
config CHARGER_SM5705
tristate "SM5705 battery charger support"
depends on BATTERY_SAMSUNG
help
Say Y here to enable support for the SM5705 charger
config CS100_JPNCONCEPT
tristate "cs100 command support"
depends on BATTERY_SAMSUNG && I2C
help
Say Y here to support for CS100 command to stop when full-charged
on wireless charging for JPN models
config AFC_CHARGER_MODE
bool "afc charging support in sec battery driver"
default n
help
Say Y to include support for sec afc charging support
config SAMSUNG_LPM_MODE
bool "Off charging mode support in sec battery driver"
default n
help
Say Y to include support for sec off charging support
This value defined at bootloader.
Before enable this feature,
implemet power off charging in the bootloader.
config SAMSUNG_BATTERY_ENG_TEST
bool "enable ENG mode for battery test"
default n
help
Say Y to include support for battery test
enable this feature only ENG mode
this featuren must disabled user binary
stability test etc..
config SAMSUNG_BATTERY_FACTORY
bool "enable for factory test"
default n
help
Say Y to include support for factory test
enable this feature only factory mode
this featuren must disabled user binary
stability test etc..
config SAMSUNG_BATTERY_DISALLOW_DEEP_SLEEP
bool "Disallow deep sleep during charging"
default n
depends on BATTERY_SAMSUNG && (ARCH_MSM8974 || ARCH_APQ8084)
help
Say Y to include support
Disallow deep sleep during charging for stablity.
config DISABLE_SAVE_CAPACITY_MAX
bool "Disable to save capacity max in efs"
default n
depends on BATTERY_SAMSUNG
help
Say Y to include support to disable it
capacity_max is saved in /efs/Battery/batt_capacity_max
capacity_max is restored after boot up
You can find the code in healthd
config SIOP_CHARGING_LIMIT_CURRENT
int "Siop charging limit current"
default 0
help
set SIOP charging limit current.
config EN_OOPS
bool "enable oops filter"
default n
help
Say Y to enable oops filter
config USE_POGO
bool "enable pogo charging"
default n
help
Say Y to enable CONFIG_USE_POGO
config MACH_KOR_EARJACK_WR
bool "enable earjack-noise workaround"
default n
depends on BATTERY_SAMSUNG
help
Say Y to enable earjack-noise workaround at charging
config STORE_MODE
bool "enable store mode"
default n
help
Say Y to enable CONFIG_STORE_MODE
config SW_SELF_DISCHARGING
bool "enable sw_self_discharging"
default n
help
Say Y to enable CONFIG_SW_SELF_DISCHARGING
config BATTERY_AGE_FORECAST
tristate "battery age forecast"
default n
depends on BATTERY_SWELLING
help
Say Y to use calc time to full function.
config BATTERY_AGE_FORECAST_DETACHABLE
tristate "battery age forecast for detachable"
default n
select BATTERY_AGE_FORECAST
help
Say Y to use battery age forecast for detachable
config ENG_BATTERY_CONCEPT
bool "enable temp block"
default n
help
Say Y to enable CONFIG_ENG_BATTERY_CONCEPT
config QH_ALGORITHM
bool "enable QH algorithm"
default n
help
Say Y to enable CONFIG_QH_ALGORITHM to measure leakges of the battery
using raw coulomb count generated by the device
config BATTERY_NOTIFIER
bool "battery notifier"
default n
help
Say Y to enable battery notifier
config LIMIT_CHARGING_DURING_CALL
bool "limit charging during call"
default n
help
Say Y to limit charging during call
config ENABLE_100MA_CHARGING_BEFORE_USB_CONFIGURED
bool "enable 100mA before usb configured"
default n
help
Say Y to enable 100mA before usb configured
config TABLET_MODEL_CONCEPT
bool "tablet model concept"
default n
help
Say Y to enable tablet model concept

View File

@@ -0,0 +1,13 @@
obj-$(CONFIG_OF) += sec_adc.o
obj-$(CONFIG_MULTI_CHARGING) += sec_multi_charger.o
obj-$(CONFIG_STEP_CHARGING) += sec_step_charging.o
obj-$(CONFIG_BATTERY_CISD) += sec_cisd.o
obj-$(CONFIG_UPDATE_BATTERY_DATA) += sec_battery_data.o
obj-$(CONFIG_BATTERY_NOTIFIER) += battery_notifier.o
obj-$(CONFIG_FUELGAUGE_SM5705) += sm5705_fuelgauge.o
obj-$(CONFIG_CHARGER_SM5705) += sm5705_charger.o sm5705_charger_oper.o
obj-$(CONFIG_BATTERY_SAMSUNG) += sec_battery.o
obj-$(CONFIG_BATTERY_SAMSUNG) += sec_battery_ttf.o

View File

@@ -0,0 +1,205 @@
#include <linux/device.h>
#include <linux/notifier.h>
#include <linux/battery/battery_notifier.h>
#include <linux/sec_class.h>
//#include <linux/sec_sysfs.h>
#define DEBUG
#define SET_BATTERY_NOTIFIER_BLOCK(nb, fn, dev) do { \
(nb)->notifier_call = (fn); \
(nb)->priority = (dev); \
} while (0)
#define DESTROY_BATTERY_NOTIFIER_BLOCK(nb) \
SET_BATTERY_NOTIFIER_BLOCK(nb, NULL, -1)
static struct charger_notifier_struct charger_notifier;
static struct pdic_notifier_struct pdic_notifier;
struct device *charger_device;
struct device *pdic_device;
int charger_notifier_register(struct notifier_block *nb, notifier_fn_t notifier,
charger_notifier_device_t listener)
{
int ret = 0;
pr_info("%s: listener=%d register\n", __func__, listener);
/* Check if CHARGER Notifier is ready. */
if (!charger_device) {
pr_err("%s: Not Initialized...\n", __func__);
return -1;
}
SET_BATTERY_NOTIFIER_BLOCK(nb, notifier, listener);
ret = blocking_notifier_chain_register(&(charger_notifier.notifier_call_chain), nb);
if (ret < 0)
pr_err("%s: blocking_notifier_chain_register error(%d)\n",
__func__, ret);
/* current charger's attached_device status notify */
nb->notifier_call(nb, charger_notifier.event,
&(charger_notifier));
return ret;
}
int charger_notifier_unregister(struct notifier_block *nb)
{
int ret = 0;
pr_info("%s: listener=%d unregister\n", __func__, nb->priority);
ret = blocking_notifier_chain_unregister(&(charger_notifier.notifier_call_chain), nb);
if (ret < 0)
pr_err("%s: blocking_notifier_chain_unregister error(%d)\n",
__func__, ret);
DESTROY_BATTERY_NOTIFIER_BLOCK(nb);
return ret;
}
int pdic_notifier_register(struct notifier_block *nb, notifier_fn_t notifier,
pdic_notifier_device_t listener)
{
int ret = 0;
pr_info("%s: listener=%d register\n", __func__, listener);
/* Check if CHARGER Notifier is ready. */
if (!pdic_device) {
pr_err("%s: Not Initialized...\n", __func__);
return -1;
}
SET_BATTERY_NOTIFIER_BLOCK(nb, notifier, listener);
ret = blocking_notifier_chain_register(&(pdic_notifier.notifier_call_chain), nb);
if (ret < 0)
pr_err("%s: blocking_notifier_chain_register error(%d)\n",
__func__, ret);
/* current pdic's attached_device status notify */
nb->notifier_call(nb, pdic_notifier.event,
&(pdic_notifier));
return ret;
}
int pdic_notifier_unregister(struct notifier_block *nb)
{
int ret = 0;
pr_info("%s: listener=%d unregister\n", __func__, nb->priority);
ret = blocking_notifier_chain_unregister(&(pdic_notifier.notifier_call_chain), nb);
if (ret < 0)
pr_err("%s: blocking_notifier_chain_unregister error(%d)\n",
__func__, ret);
DESTROY_BATTERY_NOTIFIER_BLOCK(nb);
return ret;
}
static int battery_notifier_notify(int type)
{
int ret = 0;
switch (type) {
case CHARGER_NOTIFY:
ret = blocking_notifier_call_chain(&(charger_notifier.notifier_call_chain),
charger_notifier.event, &(charger_notifier));
break;
case PDIC_NOTIFY:
ret = blocking_notifier_call_chain(&(pdic_notifier.notifier_call_chain),
pdic_notifier.event, &(pdic_notifier));
break;
default:
pr_info("%s: notify status unknown(0x%x)\n", __func__, ret);
break;
}
switch (ret) {
case NOTIFY_STOP_MASK:
case NOTIFY_BAD:
pr_err("%s: notify error occur(0x%x)\n", __func__, ret);
break;
case NOTIFY_DONE:
case NOTIFY_OK:
pr_info("%s: notify done(0x%x)\n", __func__, ret);
break;
default:
pr_info("%s: notify status unknown(0x%x)\n", __func__, ret);
break;
}
return ret;
}
static void charger_notifier_set_property(struct charger_notifier_struct * value)
{
charger_notifier.event = value->event;
switch(value->event) {
case CHARGER_NOTIFY_EVENT_AICL:
charger_notifier.aicl_status.input_current = value->aicl_status.input_current;
break;
default:
break;
}
}
void charger_notifier_call(struct charger_notifier_struct *value)
{
/* charger's event broadcast */
pr_info("%s: CHARGER_NOTIFY_EVENT :%d\n", __func__, value->event);
charger_notifier_set_property(value);
battery_notifier_notify(CHARGER_NOTIFY);
}
static void pdic_notifier_set_property(struct pdic_notifier_struct *value)
{
pdic_notifier.event = value->event;
switch(value->event) {
case PDIC_NOTIFY_EVENT_PD_SINK:
pdic_notifier.sink_status = value->sink_status;
break;
default:
break;
}
}
void pdic_notifier_call(struct pdic_notifier_struct *value)
{
/* pdic's event broadcast */
pdic_notifier_set_property(value);
battery_notifier_notify(PDIC_NOTIFY);
}
int battery_notifier_init(void)
{
int ret = 0;
pr_info("%s\n", __func__);
charger_device = sec_device_create(0, NULL, "charger_notifier");
pdic_device = sec_device_create(0, NULL, "pdic_notifier");
if (IS_ERR(charger_device)) {
pr_err("%s Failed to create device(charer_notifier)!\n", __func__);
ret = -ENODEV;
goto out;
}
if (IS_ERR(pdic_device)) {
pr_err("%s Failed to create device(pdic_notifier)!\n", __func__);
ret = -ENODEV;
goto out;
}
BLOCKING_INIT_NOTIFIER_HEAD(&(charger_notifier.notifier_call_chain));
BLOCKING_INIT_NOTIFIER_HEAD(&(pdic_notifier.notifier_call_chain));
out:
return ret;
}
device_initcall(battery_notifier_init);

View File

@@ -0,0 +1,135 @@
/*
* sm5705_charger.h
* Samsung SM5705 Charger Header
*
* Copyright (C) 2012 Samsung Electronics, 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.
*
*/
#ifndef __SM5705_CHARGER_H
#define __SM5705_CHARGER_H __FILE__
#include <linux/mfd/core.h>
#include <linux/mfd/sm5705/sm5705.h>
#include <linux/regulator/machine.h>
#include "include/sec_charging_common.h"
/* CONFIG: Kernel Feature & Target System configuration */
//#define SM5705_SUPPORT_AICL_CONTROL - New A series dosen't support, It's MUST be disabled
#define SM5705_SUPPORT_OTG_CONTROL //- New A series dosen't support, It's MUST be disabled
#if defined(CONFIG_USE_POGO)
#define SM5705_STATUS1_WPCINPOK (1 << 4)
#endif
enum {
CHIP_ID = 0,
CHARGER_OP_MODE=1,
DATA,
};
ssize_t sm5705_chg_show_attrs(struct device *dev, struct device_attribute *attr, char *buf);
ssize_t sm5705_chg_store_attrs(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
#define SM5705_CHARGER_ATTR(_name) \
{ \
.attr = {.name = #_name, .mode = 0664}, \
.show = sm5705_chg_show_attrs, \
.store = sm5705_chg_store_attrs, \
}
enum {
SM5705_MANUAL_RESET_TIME_7s = 0x1,
SM5705_MANUAL_RESET_TIME_8s = 0x2,
SM5705_MANUAL_RESET_TIME_9s = 0x3,
};
enum {
SM5705_WATCHDOG_RESET_TIME_30s = 0x0,
SM5705_WATCHDOG_RESET_TIME_60s = 0x1,
SM5705_WATCHDOG_RESET_TIME_90s = 0x2,
SM5705_WATCHDOG_RESET_TIME_120s = 0x3,
};
enum {
SM5705_TOPOFF_TIMER_10m = 0x0,
SM5705_TOPOFF_TIMER_20m = 0x1,
SM5705_TOPOFF_TIMER_30m = 0x2,
SM5705_TOPOFF_TIMER_45m = 0x3,
};
enum {
SM5705_BUCK_BOOST_FREQ_3MHz = 0x0,
SM5705_BUCK_BOOST_FREQ_2_4MHz = 0x1,
SM5705_BUCK_BOOST_FREQ_1_5MHz = 0x2,
SM5705_BUCK_BOOST_FREQ_1_8MHz = 0x3,
};
/* for VZW support */
#if defined(CONFIG_TABLET_MODEL_CONCEPT) && !defined(CONFIG_SEC_FACTORY)
#define SLOW_CHARGING_CURRENT_STANDARD 1000
#else
#define SLOW_CHARGING_CURRENT_STANDARD 400
#endif
/* SM5705 Charger - AICL reduce current configuration */
#define REDUCE_CURRENT_STEP 100
#define MINIMUM_INPUT_CURRENT 300
#define AICL_VALID_CHECK_DELAY_TIME 10
#define SM5705_EN_DISCHG_FORCE_MASK 0x02
#define SM5705_SBPS_MASK 0x07
struct sm5705_charger_data {
struct device *dev;
struct i2c_client *i2c;
struct sec_charger_platform_data *pdata;
struct power_supply *psy_chg;
struct power_supply *psy_otg;
/* for IRQ-service handling */
int irq_aicl;
int irq_vbus_pok;
int irq_wpcin_pok;
#if defined(CONFIG_USE_POGO)
int irq_wpcin_pok_pogo;
int irq_wpcin_uvlo_pogo;
#endif
int irq_topoff;
int irq_done;
int irq_otgfail;
/* for Workqueue & wake-lock, mutex process */
struct mutex charger_mutex;
struct workqueue_struct *wqueue;
struct delayed_work wpc_work;
struct delayed_work slow_chg_work;
struct delayed_work aicl_work;
#if defined(CONFIG_USE_POGO)
struct delayed_work pogo_work;
#endif
struct delayed_work topoff_work;
// temp for rev2 SW WA
struct delayed_work op_mode_switch_work; /* for WA obnormal switch case in JIG cable */
struct wake_lock wpc_wake_lock;
struct wake_lock afc_wake_lock;
#if defined(SM5705_SW_SOFT_START)
struct wake_lock softstart_wake_lock;
#endif
struct wake_lock check_slow_wake_lock;
struct wake_lock aicl_wake_lock;
/* for charging operation handling */
int status;
int charge_mode;
unsigned int is_charging;
unsigned int cable_type;
unsigned int input_current;
unsigned int charging_current;
int irq_wpcin_state;
int aicl_on;
bool topoff_pending;
// temp for rev2 SW WA
bool is_rev2_wa_done;
bool slow_late_chg_mode;
};
extern int sm5705_call_fg_device_id(void);
#if defined(SM5705_WATCHDOG_RESET_ACTIVATE)
extern void sm5705_charger_watchdog_timer_keepalive(void);
#endif
#endif /* __SM5705_CHARGER_H */

View File

@@ -0,0 +1,45 @@
/*
* drivers/battery/sm5705_charger_oper.h
*
* SM5705 Charger Operation Mode controller
*
* Copyright (C) 2015 Siliconmitus Technology Corp.
*
* 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; either version 2
* of the License, or (at your option) any later version.
*
*/
#include <linux/init.h>
#include <linux/mfd/sm5705/sm5705.h>
enum {
SM5705_CHARGER_OP_MODE_SUSPEND = 0x0,
SM5705_CHARGER_OP_MODE_FACTORY = 0x1,
SM5705_CHARGER_OP_MODE_WPC_OTG_CHG_OFF = 0x2,
SM5705_CHARGER_OP_MODE_WPC_OTG_CHG_ON = 0x3,
SM5705_CHARGER_OP_MODE_CHG_OFF = 0x4,
SM5705_CHARGER_OP_MODE_CHG_ON = 0x5,
SM5705_CHARGER_OP_MODE_FLASH_BOOST = 0x6,
SM5705_CHARGER_OP_MODE_USB_OTG = 0x7,
};
enum SM5705_CHARGER_OP_EVENT_TYPE {
SM5705_CHARGER_OP_EVENT_SUSPEND_MODE = 0x7,
SM5705_CHARGER_OP_EVENT_VBUS = 0x5,
SM5705_CHARGER_OP_EVENT_WPC = 0x4,
SM5705_CHARGER_OP_EVENT_FLASH = 0x3,
SM5705_CHARGER_OP_EVENT_TORCH = 0x2,
SM5705_CHARGER_OP_EVENT_OTG = 0x1,
SM5705_CHARGER_OP_EVENT_PWR_SHAR = 0x0,
};
#define make_OP_STATUS(vbus,wpc,flash,torch,otg,pwr_shar) (((vbus & 0x1) << SM5705_CHARGER_OP_EVENT_VBUS) | \
((wpc & 0x1) << SM5705_CHARGER_OP_EVENT_WPC) | \
((flash & 0x1) << SM5705_CHARGER_OP_EVENT_FLASH) | \
((torch & 0x1) << SM5705_CHARGER_OP_EVENT_TORCH) | \
((otg & 0x1) << SM5705_CHARGER_OP_EVENT_OTG) | \
((pwr_shar & 0x1) << SM5705_CHARGER_OP_EVENT_PWR_SHAR))
int sm5705_charger_oper_push_event(int event_type, bool enable);
int sm5705_charger_oper_table_init(struct i2c_client *i2c);
int sm5705_charger_oper_get_current_status(void);
int sm5705_charger_oper_get_current_op_mode(void);

View File

@@ -0,0 +1,220 @@
/*
* drivers/battery/sm5705_fuelgauge.h
*
* Header of SiliconMitus SM5705 Fuelgauge Driver
*
* Copyright (C) 2015 SiliconMitus
* Author: SW Jung
*
* 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; either version 2
* of the License, or (at your option) any later version.
*/
#ifndef SM5705_FUELGAUGE_H
#define SM5705_FUELGAUGE_H
#include <linux/i2c.h>
//include <linux/mfd/sm5705.h>
#include "include/sec_charging_common.h"
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#endif /* #ifdef CONFIG_DEBUG_FS */
#define FG_DRIVER_VER "0.0.0.1"
// #define ENABLE_FULL_OFFSET 1
/*To differentiate two battery Packs: SDI & ATL*/
#if defined(CONFIG_BATTERY_AGE_FORECAST)
#define ENABLE_BATT_LONG_LIFE 1
#endif
enum {
SDI_BATTERY_TYPE = 0,
ATL_BATTERY_TYPE,
UNKNOWN_TYPE
};
struct battery_data_t {
const int battery_type; /* 4200 or 4350 or 4400*/
const int battery_table[3][16];
const int rce_value[3];
const int dtcd_value;
const int rs_value[4];
const int vit_period;
const int mix_value[2];
const int topoff_soc[2];
const int volt_cal;
const int curr_cal;
const int temp_std;
const int temp_offset;
const int temp_offset_cal;
};
struct sec_fg_info {
/* Device_id */
int device_id;
/* State Of Connect */
int online;
/* battery SOC (capacity) */
int batt_soc;
/* battery voltage */
int batt_voltage;
/* battery AvgVoltage */
int batt_avgvoltage;
/* battery OCV */
int batt_ocv;
/* Current */
int batt_current;
/* battery Avg Current */
int batt_avgcurrent;
/* battery SOC cycle */
int batt_soc_cycle;
struct battery_data_t *comp_pdata;
struct mutex param_lock;
/* copy from platform data /
* DTS or update by shell script */
struct mutex io_lock;
struct device *dev;
int32_t temperature;; /* 0.1 deg C*/
int32_t temp_fg;; /* 0.1 deg C*/
/* register programming */
int reg_addr;
u8 reg_data[2];
int battery_typ; /*SDI_BATTERY_TYPE or ATL_BATTERY_TYPE*/
int batt_id_adc_check;
int battery_table[3][16];
#ifdef ENABLE_BATT_LONG_LIFE
#ifdef CONFIG_BATTERY_AGE_FORECAST_DETACHABLE
int v_max_table[3];
int q_max_table[3];
#else
int v_max_table[5];
int q_max_table[5];
#endif
int v_max_now;
int q_max_now;
#endif
int rce_value[3];
int dtcd_value;
int rs_value[5]; /*rs p_mix_factor n_mix_factor max min*/
int vit_period;
int mix_value[2]; /*mix_rate init_blank*/
int misc;
int enable_topoff_soc;
int topoff_soc;
int top_off;
int cycle_high_limit;
int cycle_low_limit;
int cycle_limit_cntl;
int enable_v_offset_cancel_p;
int enable_v_offset_cancel_n;
int v_offset_cancel_level;
int v_offset_cancel_mohm;
int volt_cal;
int curr_offset;
int p_curr_cal;
int n_curr_cal;
int curr_lcal_en;
int curr_lcal_0;
int curr_lcal_1;
int curr_lcal_2;
int en_auto_curr_offset;
int cntl_value;
#ifdef ENABLE_FULL_OFFSET
int full_offset_margin;
int full_extra_offset;
#endif
int temp_std;
int en_fg_temp_volcal;
int fg_temp_volcal_denom;
int fg_temp_volcal_fact;
int en_high_fg_temp_offset;
int high_fg_temp_offset_denom;
int high_fg_temp_offset_fact;
int en_low_fg_temp_offset;
int low_fg_temp_offset_denom;
int low_fg_temp_offset_fact;
int en_high_fg_temp_cal;
int high_fg_temp_p_cal_denom;
int high_fg_temp_p_cal_fact;
int high_fg_temp_n_cal_denom;
int high_fg_temp_n_cal_fact;
int en_low_fg_temp_cal;
int low_fg_temp_p_cal_denom;
int low_fg_temp_p_cal_fact;
int low_fg_temp_n_cal_denom;
int low_fg_temp_n_cal_fact;
int en_high_temp_cal;
int high_temp_p_cal_denom;
int high_temp_p_cal_fact;
int high_temp_n_cal_denom;
int high_temp_n_cal_fact;
int en_low_temp_cal;
int low_temp_p_cal_denom;
int low_temp_p_cal_fact;
int low_temp_n_cal_denom;
int low_temp_n_cal_fact;
int battery_type; /* 4200 or 4350 or 4400*/
int data_ver;
uint32_t soc_alert_flag : 1; /* 0 : nu-occur, 1: occur */
uint32_t volt_alert_flag : 1; /* 0 : nu-occur, 1: occur */
uint32_t flag_full_charge : 1; /* 0 : no , 1 : yes*/
uint32_t flag_chg_status : 1; /* 0 : discharging, 1: charging*/
uint32_t flag_charge_health : 1; /* 0 : no , 1 : good*/
int32_t irq_ctrl;
int value_v_alarm;
uint32_t is_FG_initialised;
int iocv_error_count;
int n_tem_poff;
int n_tem_poff_offset;
int l_tem_poff;
int l_tem_poff_offset;
/* previous battery voltage current*/
int p_batt_voltage;
int p_batt_current;
};
struct sec_fuelgauge_info {
struct i2c_client *client;
sec_battery_platform_data_t *pdata;
struct power_supply *psy_fg;
struct delayed_work isr_work;
int cable_type;
bool is_charging;
bool ta_exist;
/* HW-dedicated fuel guage info structure
* used in individual fuel gauge file only
* (ex. dummy_fuelgauge.c)
*/
struct sec_fg_info info;
bool is_fuel_alerted;
bool volt_alert_flag;
struct wake_lock fuel_alert_wake_lock;
unsigned int capacity_old; /* only for atomic calculation */
unsigned int capacity_max; /* only for dynamic calculation */
int raw_capacity;
#if defined(CONFIG_BATTERY_AGE_FORECAST)
unsigned int chg_full_soc; /* BATTERY_AGE_FORECAST */
#endif
bool initial_update_of_soc;
struct mutex fg_lock;
/* register programming */
int reg_addr;
u8 reg_data[2];
int fg_irq;
};
ssize_t sm5705_fg_show_attrs(struct device *dev, struct device_attribute *attr, char *buf);
ssize_t sm5705_fg_store_attrs(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
#ifdef CONFIG_OF
extern void board_fuelgauge_init(void *fuelgauge);
extern bool sec_bat_check_jig_status(void);
#endif
#define SM5705_FG_ATTR(_name) \
{ \
.attr = {.name = #_name, .mode = 0664}, \
.show = sm5705_fg_show_attrs, \
.store = sm5705_fg_store_attrs, \
}
enum {
FG_REG = 0,
FG_DATA,
FG_REGS,
};
#endif // SM5705_FUELGAUGE_H

View File

@@ -0,0 +1,118 @@
/*
* drivers/battery/sm5705_fuelgauge-impl.h
*
* Header of SiliconMitus SM5705 Fuelgauge Driver Implementation
*
* Copyright (C) 2015 SiliconMitus
* Author: SW Jung
*
* 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; either version 2
* of the License, or (at your option) any later version.
*/
#ifndef SM5705_FUELGAUGE_IMPL_H
#define SM5705_FUELGAUGE_IMPL_H
/* Definitions of SM5705 Fuelgauge Registers */
// I2C Register
#define SM5705_REG_DEVICE_ID 0x00
#define SM5705_REG_CNTL 0x01
#define SM5705_REG_INTFG 0x02
#define SM5705_REG_INTFG_MASK 0x03
#define SM5705_REG_STATUS 0x04
#define SM5705_REG_SOC 0x05
#define SM5705_REG_OCV 0x06
#define SM5705_REG_VOLTAGE 0x07
#define SM5705_REG_CURRENT 0x08
#define SM5705_REG_TEMPERATURE 0x09
#define SM5705_REG_SOC_CYCLE 0x0A
#define SM5705_REG_V_ALARM 0x0C
#define SM5705_REG_T_ALARM 0x0D
#define SM5705_REG_SOC_ALARM 0x0E
#define SM5705_REG_FG_OP_STATUS 0x10
#define SM5705_REG_TOPOFFSOC 0x12
#define SM5705_REG_PARAM_CTRL 0x13
#define SM5705_REG_PARAM_RUN_UPDATE 0x14
#define SM5705_REG_SOC_CYCLE_CFG 0x15
#define SM5705_REG_VIT_PERIOD 0x1A
#define SM5705_REG_MIX_RATE 0x1B
#define SM5705_REG_MIX_INIT_BLANK 0x1C
#define SM5705_REG_RESERVED 0x1F
#define SM5705_REG_RCE0 0x20
#define SM5705_REG_RCE1 0x21
#define SM5705_REG_RCE2 0x22
#define SM5705_REG_DTCD 0x23
#define SM5705_REG_AUTO_RS_MAN 0x24
#define SM5705_REG_RS_MIX_FACTOR 0x25
#define SM5705_REG_RS_MAX 0x26
#define SM5705_REG_RS_MIN 0x27
#define SM5705_REG_RS_TUNE 0x28
#define SM5705_REG_RS_MAN 0x29
//for cal
#define SM5705_REG_CURR_CAL 0x2C
#define SM5705_REG_IOCV_MAN 0x2E
#define SM5705_REG_END_V_IDX 0x2F
#define SM5705_REG_VOLT_CAL 0x50
#define SM5705_REG_CURR_OFF 0x51
#define SM5705_REG_CURR_P_SLOPE 0x52
#define SM5705_REG_CURR_N_SLOPE 0x53
#define SM5705_REG_CURRLCAL_0 0x54
#define SM5705_REG_CURRLCAL_1 0x55
#define SM5705_REG_CURRLCAL_2 0x56
//for debug
#define SM5705_REG_OCV_STATE 0x80
#define SM5705_REG_CURRENT_EST 0x85
#define SM5705_REG_CURRENT_ERR 0x86
#define SM5705_REG_Q_EST 0x87
#define SM5705_AUX_STAT 0x94
//etc
#define SM5705_REG_MISC 0x90
#define SM5705_REG_RESET 0x91
#define SM5705_FG_INIT_MARK 0xA000
#define SM5705_FG_PARAM_UNLOCK_CODE 0x3700
#define SM5705_FG_PARAM_LOCK_CODE 0x0000
#define SM5705_FG_TABLE_LEN 0xF//real table length -1
//start reg addr for table
#define SM5705_REG_TABLE_START 0xA0
#define SM5705_REG_IOCV_B_L_MIN 0x30
#define SM5705_REG_IOCV_B_L_MAX 0x35
#define SM5705_REG_IOCV_B_C_MIN 0x36
#define SM5705_REG_IOCV_B_C_MAX 0x3B
#define SM5705_REG_IOCI_B_L_MIN 0x40
#define SM5705_REG_IOCI_B_L_MAX 0x45
#define SM5705_REG_IOCI_B_C_MIN 0x46
#define SM5705_REG_IOCI_B_C_MAX 0x4B
#define SW_RESET_CODE 0x00A6
#define SW_RESET_OTP_CODE 0x01A6
#define RS_MAN_CNTL 0x0800
// control register value
#define ENABLE_MIX_MODE 0x8000
#define ENABLE_TEMP_MEASURE 0x4000
#define ENABLE_TOPOFF_SOC 0x2000
#define ENABLE_RS_MAN_MODE 0x0800
#define ENABLE_MANUAL_OCV 0x0400
#define ENABLE_MODE_nENQ4 0x0200
#define ENABLE_SOC_ALARM 0x0008
#define ENABLE_T_H_ALARM 0x0004
#define ENABLE_T_L_ALARM 0x0002
#define ENABLE_V_ALARM 0x0001
#define CNTL_REG_DEFAULT_VALUE 0x2008
#define INIT_CHECK_MASK 0x0010
#define DISABLE_RE_INIT 0x0010
#define SM5705_JIG_CONNECTED 0x0001
#define SM5705_BATTERY_VERSION 0x00F0
#define TOPOFF_SOC_97 0x111
#define TOPOFF_SOC_96 0x110
#define TOPOFF_SOC_95 0x101
#define TOPOFF_SOC_94 0x100
#define TOPOFF_SOC_93 0x011
#define TOPOFF_SOC_92 0x010
#define TOPOFF_SOC_91 0x001
#define TOPOFF_SOC_90 0x000
#define MASK_L_SOC_INT 0x0008
#define MASK_H_TEM_INT 0x0004
#define MASK_L_TEM_INT 0x0002
#define MASK_L_VOL_INT 0x0001
#define FULL_SOC 100
#endif // SM5705_FUELGAUGE_IMPL_H

View File

@@ -0,0 +1,32 @@
/*
* sec_adc.h
* Samsung Mobile Charger Header
*
* Copyright (C) 2012 Samsung Electronics, 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.
*
*/
#ifndef __SEC_ADC_H
#define __SEC_ADC_H __FILE__
#include <linux/iio/consumer.h>
#include "sec_battery.h"
#include "sec_charging_common.h"
#define VENDOR_UNKNOWN 0
#define VENDOR_LSI 1
#define VENDOR_QCOM 2
#define RETRY_CNT 3
#endif /* __SEC_ADC_H */

View File

@@ -0,0 +1,658 @@
/*
* sec_battery.h
* Samsung Mobile Battery Header
*
*
* Copyright (C) 2012 Samsung Electronics, 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.
*
*/
#ifndef __SEC_BATTERY_H
#define __SEC_BATTERY_H __FILE__
#include "sec_charging_common.h"
#include <linux/of_gpio.h>
#include <linux/alarmtimer.h>
#include <linux/wakelock.h>
#include <linux/workqueue.h>
#include <linux/proc_fs.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/qpnp/qpnp-adc.h>
#if defined(CONFIG_USB_TYPEC_MANAGER_NOTIFIER)
#include <linux/usb/manager/usb_typec_manager_notifier.h>
#else
#if defined(CONFIG_CCIC_NOTIFIER)
#include <linux/ccic/ccic_notifier.h>
#endif /* CONFIG_CCIC_NOTIFIER */
#if defined(CONFIG_MUIC_NOTIFIER)
#include <linux/muic/muic.h>
#include <linux/muic/muic_notifier.h>
#else
#include <linux/muic/muic.h>
#endif
#endif
#if defined(CONFIG_BATTERY_NOTIFIER)
#include <linux/battery/battery_notifier.h>
#endif
#if defined(CONFIG_VBUS_NOTIFIER)
#include <linux/vbus_notifier.h>
#endif
#if defined(CONFIG_BATTERY_CISD)
#include "sec_cisd.h"
#endif
#include "sec_adc.h"
#define SEC_BAT_CURRENT_EVENT_NONE 0x00000
#define SEC_BAT_CURRENT_EVENT_AFC 0x00001
#define SEC_BAT_CURRENT_EVENT_CHARGE_DISABLE 0x00002
#define SEC_BAT_CURRENT_EVENT_SKIP_HEATING_CONTROL 0x00004
#define SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING 0x00010
#define SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING 0x00020
#if defined(CONFIG_ENABLE_100MA_CHARGING_BEFORE_USB_CONFIGURED)
#define SEC_BAT_CURRENT_EVENT_USB_100MA 0x00040
#else
#define SEC_BAT_CURRENT_EVENT_USB_100MA 0x00000
#endif
#define SEC_BAT_CURRENT_EVENT_LOW_TEMP 0x00080
#define SEC_BAT_CURRENT_EVENT_SWELLING_MODE (SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING | SEC_BAT_CURRENT_EVENT_LOW_TEMP | SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING)
#define SEC_BAT_CURRENT_EVENT_USB_SUPER 0x00100
#define SEC_BAT_CURRENT_EVENT_CHG_LIMIT 0x00200
#define SEC_BAT_CURRENT_EVENT_CALL 0x00400
#define SEC_BAT_CURRENT_EVENT_SLATE 0x00800
#define SEC_BAT_CURRENT_EVENT_VBAT_OVP 0x01000
#define SEC_BAT_CURRENT_EVENT_VSYS_OVP 0x02000
#define SEC_BAT_CURRENT_EVENT_WPC_VOUT_LOCK 0x04000
#define SEC_BAT_CURRENT_EVENT_AICL 0x08000
#define SEC_BAT_CURRENT_EVENT_HV_DISABLE 0x10000
#define SEC_BAT_CURRENT_EVENT_SELECT_PDO 0x020000
#define SIOP_EVENT_NONE 0x0000
#define SIOP_EVENT_WPC_CALL 0x0001
#if defined(CONFIG_SEC_FACTORY) // SEC_FACTORY
#define STORE_MODE_CHARGING_MAX 80
#define STORE_MODE_CHARGING_MIN 70
#else // !SEC_FACTORY, STORE MODE
#define STORE_MODE_CHARGING_MAX 70
#define STORE_MODE_CHARGING_MIN 60
#define STORE_MODE_CHARGING_MAX_VZW 35
#define STORE_MODE_CHARGING_MIN_VZW 30
#endif //(CONFIG_SEC_FACTORY)
#define ADC_CH_COUNT 10
#define ADC_SAMPLE_COUNT 10
#define DEFAULT_HEALTH_CHECK_COUNT 5
#define TEMP_HIGHLIMIT_DEFAULT 2000
#define SIOP_INPUT_LIMIT_CURRENT 1200
#define SIOP_CHARGING_LIMIT_CURRENT 1000
#define SIOP_WIRELESS_INPUT_LIMIT_CURRENT 530
#define SIOP_WIRELESS_CHARGING_LIMIT_CURRENT 780
#define SIOP_HV_WIRELESS_INPUT_LIMIT_CURRENT 700
#define SIOP_HV_WIRELESS_CHARGING_LIMIT_CURRENT 600
#define SIOP_STORE_HV_WIRELESS_CHARGING_LIMIT_CURRENT 450
#define SIOP_HV_INPUT_LIMIT_CURRENT 1200
#define SIOP_HV_CHARGING_LIMIT_CURRENT 1000
#define SIOP_HV_12V_INPUT_LIMIT_CURRENT 535
#define SIOP_HV_12V_CHARGING_LIMIT_CURRENT 1000
#define BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE 0x00000001
#define BATT_MISC_EVENT_WIRELESS_BACKPACK_TYPE 0x00000002
#define BATT_MISC_EVENT_TIMEOUT_OPEN_TYPE 0x00000004
#define BATT_MISC_EVENT_BATT_RESET_SOC 0x00000008
#define BATT_MISC_EVENT_HICCUP_TYPE 0x00000020
#define BATT_MISC_EVENT_WIRELESS_FOD 0x00000100
#define BATT_MISC_EVENT_HEALTH_OVERHEATLIMIT 0x00100000
#define SEC_INPUT_VOLTAGE_0V 0
#define SEC_INPUT_VOLTAGE_5V 5
#define SEC_INPUT_VOLTAGE_9V 9
#define SEC_INPUT_VOLTAGE_10V 10
#define SEC_INPUT_VOLTAGE_12V 12
#define HV_CHARGER_STATUS_STANDARD1 12000 /* mW */
#define HV_CHARGER_STATUS_STANDARD2 20000 /* mW */
#if defined(CONFIG_CCIC_NOTIFIER)
struct sec_bat_pdic_info {
unsigned int input_voltage;
unsigned int input_current;
unsigned int pdo_index;
};
struct sec_bat_pdic_list {
struct sec_bat_pdic_info pd_info[8]; /* 5V ~ 12V */
unsigned int now_pd_index;
unsigned int max_pd_count;
};
#endif
#if defined(CONFIG_BATTERY_SWELLING)
enum swelling_mode_state {
SWELLING_MODE_NONE = 0,
SWELLING_MODE_CHARGING,
SWELLING_MODE_FULL,
};
#endif
struct adc_sample_info {
unsigned int cnt;
int total_adc;
int average_adc;
int adc_arr[ADC_SAMPLE_COUNT];
int index;
};
struct sec_ttf_data;
struct sec_battery_info {
struct device *dev;
sec_battery_platform_data_t *pdata;
struct sec_ttf_data *ttf_d;
/* power supply used in Android */
struct power_supply *psy_bat;
struct power_supply *psy_usb;
struct power_supply *psy_ac;
struct power_supply *psy_wireless;
struct power_supply *psy_ps;
#if defined(CONFIG_USE_POGO)
struct power_supply *psy_pogo;
#endif
unsigned int irq;
int pd_usb_attached;
#if defined(CONFIG_USB_TYPEC_MANAGER_NOTIFIER)
struct notifier_block usb_typec_nb;
#else
#if defined(CONFIG_CCIC_NOTIFIER)
struct notifier_block pdic_nb;
#endif
#if defined(CONFIG_MUIC_NOTIFIER)
struct notifier_block batt_nb;
#endif
#endif
#if defined(CONFIG_CCIC_NOTIFIER)
bool pdic_attach;
bool pdic_ps_rdy;
struct pdic_notifier_struct pdic_info;
struct sec_bat_pdic_list pd_list;
#endif
bool update_pd_list;
#if defined(CONFIG_VBUS_NOTIFIER)
struct notifier_block vbus_nb;
int muic_vbus_status;
#endif
bool is_sysovlo;
bool is_vbatovlo;
bool safety_timer_set;
bool lcd_status;
bool skip_swelling;
int status;
int health;
bool present;
int voltage_now; /* cell voltage (mV) */
int voltage_avg; /* average voltage (mV) */
int voltage_ocv; /* open circuit voltage (mV) */
int current_now; /* current (mA) */
int inbat_adc; /* inbat adc */
int current_avg; /* average current (mA) */
int current_max; /* input current limit (mA) */
int current_adc;
unsigned int capacity; /* SOC (%) */
unsigned int input_voltage; /* CHGIN/WCIN input voltage (V) */
unsigned int charge_power; /* charge power (mW) */
unsigned int max_charge_power; /* max charge power (mW) */
unsigned int pd_max_charge_power; /* max charge power for pd (mW) */
unsigned int aicl_current;
struct mutex adclock;
struct adc_sample_info adc_sample[ADC_CH_COUNT];
/* keep awake until monitor is done */
struct wake_lock monitor_wake_lock;
struct workqueue_struct *monitor_wqueue;
struct delayed_work monitor_work;
#ifdef CONFIG_SAMSUNG_BATTERY_FACTORY
struct wake_lock lpm_wake_lock;
#endif
unsigned int polling_count;
unsigned int polling_time;
bool polling_in_sleep;
bool polling_short;
struct delayed_work polling_work;
struct alarm polling_alarm;
ktime_t last_poll_time;
#if defined(CONFIG_BATTERY_CISD)
struct cisd cisd;
bool skip_cisd;
bool usb_overheat_check;
int prev_volt;
int prev_temp;
int prev_jig_on;
int enable_update_data;
int prev_chg_on;
#endif
/* battery check */
unsigned int check_count;
/* ADC check */
unsigned int check_adc_count;
unsigned int check_adc_value;
/* health change check*/
bool health_change;
/* ovp-uvlo health check */
int health_check_count;
/* time check */
unsigned long charging_start_time;
unsigned long charging_passed_time;
unsigned long charging_next_time;
unsigned long charging_fullcharged_time;
unsigned long wc_heating_start_time;
unsigned long wc_heating_passed_time;
unsigned int wc_heat_limit;
/* chg temperature check */
unsigned int chg_limit;
unsigned int chg_limit_recovery_cable;
unsigned int vbus_chg_by_siop;
unsigned int vbus_chg_by_full;
unsigned int mix_limit;
unsigned int vbus_limit;
/* temperature check */
int temperature; /* battery temperature */
#if defined(CONFIG_ENG_BATTERY_CONCEPT)
int temperature_test_battery;
int temperature_test_usb;
int temperature_test_wpc;
int temperature_test_chg;
int temperature_test_blkt;
#endif
int temper_amb; /* target temperature */
int usb_temp;
int chg_temp; /* charger temperature */
int wpc_temp;
int coil_temp;
int slave_chg_temp;
int blkt_temp; /* blanket temperature(instead of batt temp in mix_temp func for tablet model) */
int temp_adc;
int temp_ambient_adc;
int usb_temp_adc;
int chg_temp_adc;
int wpc_temp_adc;
int coil_temp_adc;
int slave_chg_temp_adc;
int blkt_temp_adc;
int temp_highlimit_threshold;
int temp_highlimit_recovery;
int temp_high_threshold;
int temp_high_recovery;
int temp_low_threshold;
int temp_low_recovery;
/* charging */
unsigned int charging_mode;
bool is_recharging;
int wdt_kick_disable;
bool is_jig_on;
int cable_type;
int muic_cable_type;
int extended_cable_type;
struct wake_lock cable_wake_lock;
struct delayed_work cable_work;
struct wake_lock vbus_wake_lock;
struct delayed_work siop_work;
struct wake_lock siop_wake_lock;
struct wake_lock afc_wake_lock;
struct delayed_work afc_work;
#if defined(CONFIG_WIRELESS_FIRMWARE_UPDATE)
struct delayed_work update_work;
struct delayed_work fw_init_work;
#endif
struct delayed_work siop_event_work;
struct wake_lock siop_event_wake_lock;
struct delayed_work siop_level_work;
struct wake_lock siop_level_wake_lock;
struct delayed_work wc_headroom_work;
struct wake_lock wc_headroom_wake_lock;
#if defined(CONFIG_UPDATE_BATTERY_DATA)
struct delayed_work batt_data_work;
struct wake_lock batt_data_wake_lock;
char *data_path;
#endif
#ifdef CONFIG_OF
struct delayed_work parse_mode_dt_work;
struct wake_lock parse_mode_dt_wake_lock;
#endif
struct delayed_work init_chg_work;
char batt_type[48];
unsigned int full_check_cnt;
unsigned int recharge_check_cnt;
struct mutex iolock;
int input_current;
int charging_current;
int topoff_current;
int wpc_vout_level;
unsigned int current_event;
/* wireless charging enable */
struct mutex wclock;
int wc_enable;
int wc_enable_cnt;
int wc_enable_cnt_value;
int led_cover;
int wc_status;
bool wc_cv_mode;
bool wc_pack_max_curr;
int wire_status;
#if defined(CONFIG_USE_POGO)
/* pogo status */
int pogo_status;
#endif
/* wearable charging */
int ps_status;
int ps_enable;
/* test mode */
int test_mode;
bool factory_mode;
bool store_mode;
/* MTBF test for CMCC */
bool is_hc_usb;
int siop_level;
int siop_event;
int siop_prev_event;
int stability_test;
int eng_not_full_status;
bool skip_chg_temp_check;
bool skip_wpc_temp_check;
bool wpc_temp_mode;
#if defined(CONFIG_BATTERY_SWELLING)
unsigned int swelling_mode;
#endif
#if defined(CONFIG_AFC_CHARGER_MODE)
char *hv_chg_name;
#endif
#if defined(CONFIG_ENABLE_100MA_CHARGING_BEFORE_USB_CONFIGURED)
struct delayed_work slowcharging_work;
#endif
#if defined(CONFIG_BATTERY_AGE_FORECAST)
int batt_cycle;
#endif
#if defined(CONFIG_STEP_CHARGING)
unsigned int step_charging_type;
unsigned int step_charging_charge_power;
int step_charging_status;
int step_charging_step;
#endif
#if defined(CONFIG_ENG_BATTERY_CONCEPT) || defined(CONFIG_SEC_FACTORY)
bool cooldown_mode;
#endif
struct mutex misclock;
unsigned int misc_event;
unsigned int prev_misc_event;
struct delayed_work misc_event_work;
struct wake_lock misc_event_wake_lock;
struct mutex batt_handlelock;
struct mutex current_eventlock;
struct mutex typec_notylock;
unsigned int hiccup_status;
bool stop_timer;
unsigned long prev_safety_time;
unsigned long expired_time;
unsigned long cal_safety_time;
int fg_reset;
};
ssize_t sec_bat_show_attrs(struct device *dev, struct device_attribute *attr, char *buf);
ssize_t sec_bat_store_attrs(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
#define SEC_BATTERY_ATTR(_name) \
{ \
.attr = {.name = #_name, .mode = 0664}, \
.show = sec_bat_show_attrs, \
.store = sec_bat_store_attrs, \
}
/* event check */
#define EVENT_NONE (0)
#define EVENT_2G_CALL (0x1 << 0)
#define EVENT_3G_CALL (0x1 << 1)
#define EVENT_MUSIC (0x1 << 2)
#define EVENT_VIDEO (0x1 << 3)
#define EVENT_BROWSER (0x1 << 4)
#define EVENT_HOTSPOT (0x1 << 5)
#define EVENT_CAMERA (0x1 << 6)
#define EVENT_CAMCORDER (0x1 << 7)
#define EVENT_DATA_CALL (0x1 << 8)
#define EVENT_WIFI (0x1 << 9)
#define EVENT_WIBRO (0x1 << 10)
#define EVENT_LTE (0x1 << 11)
#define EVENT_LCD (0x1 << 12)
#define EVENT_GPS (0x1 << 13)
enum {
BATT_RESET_SOC = 0,
BATT_READ_RAW_SOC,
BATT_READ_ADJ_SOC,
BATT_TYPE,
BATT_VFOCV,
BATT_VOL_ADC,
BATT_VOL_ADC_CAL,
BATT_VOL_AVER,
BATT_VOL_ADC_AVER,
BATT_CURRENT_UA_NOW,
BATT_CURRENT_UA_AVG,
BATT_FILTER_CFG,
BATT_TEMP,
BATT_TEMP_ADC,
BATT_TEMP_AVER,
BATT_TEMP_ADC_AVER,
USB_TEMP,
USB_TEMP_ADC,
BATT_CHG_TEMP,
BATT_CHG_TEMP_ADC,
SLAVE_CHG_TEMP,
SLAVE_CHG_TEMP_ADC,
BLKT_TEMP,
BLKT_TEMP_ADC,
BATT_VF_ADC,
BATT_SLATE_MODE,
BATT_LP_CHARGING,
SIOP_ACTIVATED,
SIOP_LEVEL,
SIOP_EVENT,
BATT_CHARGING_SOURCE,
FG_REG_DUMP,
FG_RESET_CAP,
FG_CAPACITY,
FG_ASOC,
AUTH,
CHG_CURRENT_ADC,
WC_ADC,
WC_STATUS,
WC_ENABLE,
WC_CONTROL,
WC_CONTROL_CNT,
LED_COVER,
HV_CHARGER_STATUS,
HV_WC_CHARGER_STATUS,
HV_CHARGER_SET,
FACTORY_MODE,
STORE_MODE,
UPDATE,
TEST_MODE,
BATT_EVENT_CALL,
BATT_EVENT_2G_CALL,
BATT_EVENT_TALK_GSM,
BATT_EVENT_3G_CALL,
BATT_EVENT_TALK_WCDMA,
BATT_EVENT_MUSIC,
BATT_EVENT_VIDEO,
BATT_EVENT_BROWSER,
BATT_EVENT_HOTSPOT,
BATT_EVENT_CAMERA,
BATT_EVENT_CAMCORDER,
BATT_EVENT_DATA_CALL,
BATT_EVENT_WIFI,
BATT_EVENT_WIBRO,
BATT_EVENT_LTE,
BATT_EVENT_LCD,
BATT_EVENT_GPS,
BATT_EVENT,
BATT_TEMP_TABLE,
BATT_HIGH_CURRENT_USB,
#if defined(CONFIG_ENG_BATTERY_CONCEPT)
TEST_CHARGE_CURRENT,
#endif
SET_STABILITY_TEST,
BATT_CAPACITY_MAX,
BATT_INBAT_VOLTAGE,
BATT_INBAT_VOLTAGE_OCV,
CHECK_SLAVE_CHG,
BATT_INBAT_WIRELESS_CS100,
HMT_TA_CONNECTED,
HMT_TA_CHARGE,
#if defined(CONFIG_BATTERY_AGE_FORECAST)
FG_CYCLE,
FG_FULL_VOLTAGE,
FG_FULLCAPNOM,
BATTERY_CYCLE,
#if defined(CONFIG_BATTERY_AGE_FORECAST_DETACHABLE)
BATT_AFTER_MANUFACTURED,
#endif
#endif
BATT_WPC_TEMP,
BATT_WPC_TEMP_ADC,
BATT_COIL_TEMP,
BATT_COIL_TEMP_ADC,
BATT_WIRELESS_MST_SWITCH_TEST,
#if defined(CONFIG_WIRELESS_FIRMWARE_UPDATE)
BATT_WIRELESS_FIRMWARE_UPDATE,
OTP_FIRMWARE_RESULT,
WC_IC_GRADE,
OTP_FIRMWARE_VER_BIN,
OTP_FIRMWARE_VER,
TX_FIRMWARE_RESULT,
TX_FIRMWARE_VER,
BATT_TX_STATUS,
#endif
WC_VOUT,
WC_VRECT,
#if defined(CONFIG_WIRELESS_CHARGER_HIGH_VOLTAGE)
BATT_HV_WIRELESS_STATUS,
BATT_HV_WIRELESS_PAD_CTRL,
#endif
WC_OP_FREQ,
WC_CMD_INFO,
BATT_TUNE_FLOAT_VOLTAGE,
BATT_TUNE_INPUT_CHARGE_CURRENT,
BATT_TUNE_FAST_CHARGE_CURRENT,
BATT_TUNE_UI_TERM_CURRENT_1ST,
BATT_TUNE_UI_TERM_CURRENT_2ND,
BATT_TUNE_TEMP_HIGH_NORMAL,
BATT_TUNE_TEMP_HIGH_REC_NORMAL,
BATT_TUNE_TEMP_LOW_NORMAL,
BATT_TUNE_TEMP_LOW_REC_NORMAL,
BATT_TUNE_CHG_TEMP_HIGH,
BATT_TUNE_CHG_TEMP_REC,
BATT_TUNE_CHG_LIMMIT_CUR,
BATT_TUNE_COIL_TEMP_HIGH,
BATT_TUNE_COIL_TEMP_REC,
BATT_TUNE_COIL_LIMMIT_CUR,
#if defined(CONFIG_UPDATE_BATTERY_DATA)
BATT_UPDATE_DATA,
#endif
BATT_MISC_EVENT,
BATT_EXT_DEV_CHG,
BATT_WDT_CONTROL,
MODE,
CHECK_PS_READY,
BATT_CHIP_ID,
CISD_FULLCAPREP_MAX,
#if defined(CONFIG_BATTERY_CISD)
CISD_DATA,
CISD_DATA_JSON,
CISD_DATA_D_JSON,
CISD_WIRE_COUNT,
CISD_WC_DATA,
CISD_WC_DATA_JSON,
PREV_BATTERY_DATA,
PREV_BATTERY_INFO,
#endif
SAFETY_TIMER_SET,
BATT_SWELLING_CONTROL,
SAFETY_TIMER_INFO,
#if defined(CONFIG_ENG_BATTERY_CONCEPT)
BATT_TEMP_TEST,
#endif
BATT_CURRENT_EVENT,
CHARGE_OTG_CONTROL,
CHARGE_UNO_CONTROL,
};
enum {
EXT_DEV_NONE = 0,
EXT_DEV_GAMEPAD_CHG,
EXT_DEV_GAMEPAD_OTG,
};
extern unsigned int lpcharge;
extern void select_pdo(int num);
extern int adc_read(struct sec_battery_info *battery, int channel);
extern void adc_init(struct platform_device *pdev, struct sec_battery_info *battery);
extern void adc_exit(struct sec_battery_info *battery);
extern void sec_cable_init(struct platform_device *pdev, struct sec_battery_info *battery);
extern int sec_bat_get_adc_data(struct sec_battery_info *battery, int adc_ch, int count);
extern int sec_bat_get_charger_type_adc(struct sec_battery_info *battery);
extern bool sec_bat_get_value_by_adc(struct sec_battery_info *battery, enum sec_battery_adc_channel channel, union power_supply_propval *value);
extern int sec_bat_get_adc_value(struct sec_battery_info *battery, int channel);
extern bool sec_bat_check_vf_adc(struct sec_battery_info *battery);
#if defined(CONFIG_UPDATE_BATTERY_DATA)
extern int sec_battery_update_data(const char* file_path);
#endif
#if defined(CONFIG_BATTERY_CISD)
extern bool sec_bat_cisd_check(struct sec_battery_info *battery);
extern void sec_battery_cisd_init(struct sec_battery_info *battery);
extern void set_cisd_pad_data(struct sec_battery_info *battery, const char* buf);
#endif
bool sec_bat_hv_wc_normal_mode_check(struct sec_battery_info *battery);
#endif /* __SEC_BATTERY_H */

View File

@@ -0,0 +1,67 @@
/*
* sec_battery.h
* Samsung Mobile Battery Header
*
*
* Copyright (C) 2012 Samsung Electronics, 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.
*
*/
#ifndef __SEC_BATTERY_TTF_H
#define __SEC_BATTERY_TTF_H __FILE__
struct sec_cv_slope {
int fg_current;
int soc;
int time;
};
struct sec_battery_info;
struct sec_ttf_data {
void *pdev;
int timetofull;
unsigned int ttf_hv_12v_charge_current;
unsigned int ttf_hv_charge_current;
unsigned int ttf_hv_12v_wireless_charge_current;
unsigned int ttf_hv_wireless_charge_current;
unsigned int ttf_wireless_charge_current;
unsigned int ttf_dc25_charge_current;
unsigned int ttf_dc45_charge_current;
unsigned int ttf_predict_wc20_charge_current;
#if defined(CONFIG_USE_POGO)
unsigned int ttf_pogo_charge_current;
#endif
unsigned int max_charging_current;
unsigned int pd_charging_charge_power;
struct sec_cv_slope *cv_data;
int cv_data_length;
unsigned int ttf_capacity;
struct delayed_work timetofull_work;
};
int sec_calc_ttf(struct sec_battery_info *battery, unsigned int ttf_curr);
extern void sec_bat_calc_time_to_full(struct sec_battery_info *battery);
extern void sec_bat_time_to_full_work(struct work_struct *work);
extern void ttf_init(struct sec_battery_info *battery);
extern void ttf_work_start(struct sec_battery_info *battery);
extern int ttf_display(struct sec_battery_info *battery);
#ifdef CONFIG_OF
int sec_ttf_parse_dt(struct sec_battery_info *battery);
#endif
#endif /* __SEC_BATTERY_H */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,314 @@
/*
* sec_cisd.h
* Samsung Mobile Charger Header
*
* Copyright (C) 2015 Samsung Electronics, 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.
*
*/
#ifndef __SEC_CISD_H
#define __SEC_CISD_H __FILE__
#define CISD_STATE_NONE 0x00
#define CISD_STATE_CAP_OVERFLOW 0x01
#define CISD_STATE_VOLT_DROP 0x02
#define CISD_STATE_SOC_DROP 0x04
#define CISD_STATE_RESET 0x08
#define CISD_STATE_LEAK_A 0x10
#define CISD_STATE_LEAK_B 0x20
#define CISD_STATE_LEAK_C 0x40
#define CISD_STATE_LEAK_D 0x80
#define CISD_STATE_OVER_VOLTAGE 0x100
#define CISD_STATE_LEAK_E 0x200
#define CISD_STATE_LEAK_F 0x400
#define CISD_STATE_LEAK_G 0x800
#define is_cisd_check_type(cable_type) ( \
cable_type == SEC_BATTERY_CABLE_TA || \
cable_type == SEC_BATTERY_CABLE_9V_TA || \
cable_type == SEC_BATTERY_CABLE_9V_UNKNOWN || \
cable_type == SEC_BATTERY_CABLE_9V_ERR || \
cable_type == SEC_BATTERY_CABLE_PDIC)
#if 0
enum cisd_data {
CISD_DATA_FULL_COUNT = 0,
CISD_DATA_CAP_MAX,
CISD_DATA_CAP_MIN,
CISD_DATA_CAP_ONCE,
CISD_DATA_LEAKAGE_A,
CISD_DATA_LEAKAGE_B,
CISD_DATA_LEAKAGE_C,
CISD_DATA_LEAKAGE_D,
CISD_DATA_CAP_PER_TIME,
CISD_DATA_ERRCAP_LOW,
CISD_DATA_ERRCAP_HIGH,
CISD_DATA_OVER_VOLTAGE,
CISD_DATA_LEAKAGE_E,
CISD_DATA_LEAKAGE_F,
CISD_DATA_LEAKAGE_G,
CISD_DATA_RECHARGING_TIME,
CISD_DATA_VALERT_COUNT,
CISD_DATA_CYCLE,
CISD_DATA_WIRE_COUNT,
CISD_DATA_WIRELESS_COUNT,
CISD_DATA_HIGH_TEMP_SWELLING,
CISD_DATA_LOW_TEMP_SWELLING,
CISD_DATA_SWELLING_CHARGING_COUNT,
CISD_DATA_SAFETY_TIMER_3,
CISD_DATA_SAFETY_TIMER_5,
CISD_DATA_SAFETY_TIMER_10,
CISD_DATA_AICL_COUNT,
CISD_DATA_BATT_TEMP_MAX,
CISD_DATA_BATT_TEMP_MIN,
CISD_DATA_CHG_TEMP_MAX,
CISD_DATA_CHG_TEMP_MIN,
CISD_DATA_WPC_TEMP_MAX,
CISD_DATA_WPC_TEMP_MIN,
CISD_UNSAFE_VOLTAGE,
CISD_UNSAFE_TEMPERATURE,
CISD_SAFETY_TIMER,
CISD_VSYS_OVP,
CISD_VBAT_OVP,
CISD_WATER_DETECT,
CISD_AFC_FAIL,
CISD_DATA_MAX,
};
#endif
enum cisd_data {
CISD_DATA_RESET_ALG = 0,
CISD_DATA_ALG_INDEX,
CISD_DATA_FULL_COUNT,
CISD_DATA_CAP_MAX,
CISD_DATA_CAP_MIN,
CISD_DATA_RECHARGING_COUNT,
CISD_DATA_VALERT_COUNT,
CISD_DATA_CYCLE,
CISD_DATA_WIRE_COUNT,
CISD_DATA_WIRELESS_COUNT,
CISD_DATA_HIGH_TEMP_SWELLING,
CISD_DATA_LOW_TEMP_SWELLING,
CISD_DATA_SWELLING_CHARGING_COUNT,
CISD_DATA_SWELLING_FULL_CNT,
CISD_DATA_SWELLING_RECOVERY_CNT,
CISD_DATA_AICL_COUNT,
CISD_DATA_BATT_TEMP_MAX,
CISD_DATA_BATT_TEMP_MIN,
CISD_DATA_CHG_TEMP_MAX,
CISD_DATA_CHG_TEMP_MIN,
CISD_DATA_WPC_TEMP_MAX,
CISD_DATA_WPC_TEMP_MIN,
CISD_DATA_USB_TEMP_MAX,
CISD_DATA_USB_TEMP_MIN,
CISD_DATA_CHG_BATT_TEMP_MAX,
CISD_DATA_CHG_BATT_TEMP_MIN,
CISD_DATA_CHG_CHG_TEMP_MAX,
CISD_DATA_CHG_CHG_TEMP_MIN,
CISD_DATA_CHG_WPC_TEMP_MAX,
CISD_DATA_CHG_WPC_TEMP_MIN,
CISD_DATA_CHG_USB_TEMP_MAX,
CISD_DATA_CHG_USB_TEMP_MIN,
CISD_DATA_USB_OVERHEAT_CHARGING, /* 32 */
CISD_DATA_UNSAFETY_VOLTAGE,
CISD_DATA_UNSAFETY_TEMPERATURE,
CISD_DATA_SAFETY_TIMER,
CISD_DATA_VSYS_OVP,
CISD_DATA_VBAT_OVP,
CISD_DATA_AFC_FAIL,
CISD_DATA_BUCK_OFF,
CISD_DATA_WATER_DETECT,
CISD_DATA_DROP_VALUE,
CISD_DATA_MAX,
};
enum cisd_data_per_day {
CISD_DATA_FULL_COUNT_PER_DAY = CISD_DATA_MAX,
CISD_DATA_CAP_MAX_PER_DAY,
CISD_DATA_CAP_MIN_PER_DAY,
CISD_DATA_RECHARGING_COUNT_PER_DAY,
CISD_DATA_VALERT_COUNT_PER_DAY,
CISD_DATA_WIRE_COUNT_PER_DAY,
CISD_DATA_WIRELESS_COUNT_PER_DAY,
CISD_DATA_HIGH_TEMP_SWELLING_PER_DAY,
CISD_DATA_LOW_TEMP_SWELLING_PER_DAY,
CISD_DATA_SWELLING_CHARGING_COUNT_PER_DAY,
CISD_DATA_SWELLING_FULL_CNT_PER_DAY,
CISD_DATA_SWELLING_RECOVERY_CNT_PER_DAY,
CISD_DATA_AICL_COUNT_PER_DAY,
CISD_DATA_BATT_TEMP_MAX_PER_DAY,
CISD_DATA_BATT_TEMP_MIN_PER_DAY,
CISD_DATA_CHG_TEMP_MAX_PER_DAY,
CISD_DATA_CHG_TEMP_MIN_PER_DAY,
CISD_DATA_WPC_TEMP_MAX_PER_DAY,
CISD_DATA_WPC_TEMP_MIN_PER_DAY,
CISD_DATA_USB_TEMP_MAX_PER_DAY,
CISD_DATA_USB_TEMP_MIN_PER_DAY,
CISD_DATA_CHG_BATT_TEMP_MAX_PER_DAY,
CISD_DATA_CHG_BATT_TEMP_MIN_PER_DAY,
CISD_DATA_CHG_CHG_TEMP_MAX_PER_DAY,
CISD_DATA_CHG_CHG_TEMP_MIN_PER_DAY,
CISD_DATA_CHG_WPC_TEMP_MAX_PER_DAY,
CISD_DATA_CHG_WPC_TEMP_MIN_PER_DAY,
CISD_DATA_CHG_USB_TEMP_MAX_PER_DAY,
CISD_DATA_CHG_USB_TEMP_MIN_PER_DAY,
CISD_DATA_USB_OVERHEAT_CHARGING_PER_DAY,
CISD_DATA_UNSAFE_VOLTAGE_PER_DAY,
CISD_DATA_UNSAFE_TEMPERATURE_PER_DAY,
CISD_DATA_SAFETY_TIMER_PER_DAY, /* 32 */
CISD_DATA_VSYS_OVP_PER_DAY,
CISD_DATA_VBAT_OVP_PER_DAY,
CISD_DATA_AFC_FAIL_PER_DAY,
CISD_DATA_BUCK_OFF_PER_DAY,
CISD_DATA_WATER_DETECT_PER_DAY,
CISD_DATA_DROP_VALUE_PER_DAY,
CISD_DATA_MAX_PER_DAY,
};
enum {
WC_DATA_INDEX = 0,
WC_SNGL_NOBLE,
WC_SNGL_VEHICLE,
WC_SNGL_MINI,
WC_SNGL_ZERO,
WC_SNGL_DREAM,
WC_STAND_HERO,
WC_STAND_DREAM,
WC_EXT_PACK,
WC_EXT_PACK_TA,
WC_DATA_MAX,
};
enum {
CISD_CABLE_INDEX = 0,
CISD_CABLE_TA,
CISD_CABLE_AFC,
CISD_CABLE_AFC_FAIL,
CISD_CABLE_QC,
CISD_CABLE_QC_FAIL,
CISD_CABLE_PD,
CISD_CABLE_PD_HIGH,
CISD_CABLE_TYPE_MAX,
};
extern const char *cisd_data_str[];
extern const char *cisd_data_str_d[];
#define PAD_INDEX_STRING "INDEX"
#define PAD_INDEX_VALUE 1
#define PAD_JSON_STRING "PAD_0x"
#define MAX_PAD_ID 0xFF
struct pad_data {
unsigned int id;
unsigned int count;
struct pad_data* prev;
struct pad_data* next;
};
struct cisd {
unsigned int cisd_alg_index;
unsigned int state;
unsigned int delay_time;
int diff_volt_now;
int diff_cap_now;
int curr_cap_max;
int err_cap_max_thrs;
int err_cap_high_thr;
int err_cap_low_thr;
int overflow_cap_thr;
unsigned int cc_delay_time;
unsigned int full_delay_time;
unsigned int lcd_off_delay_time;
unsigned int recharge_delay_time;
unsigned int diff_time;
unsigned long cc_start_time;
unsigned long full_start_time;
unsigned long lcd_off_start_time;
unsigned long overflow_start_time;
unsigned long charging_end_time;
unsigned long charging_end_time_2;
unsigned int recharge_count;
unsigned int recharge_count_2;
unsigned int recharge_count_thres;
unsigned long leakage_e_time;
unsigned long leakage_f_time;
unsigned long leakage_g_time;
int current_max_thres;
int charging_current_thres;
int current_avg_thres;
unsigned int ab_vbat_max_count;
unsigned int ab_vbat_check_count;
unsigned int max_voltage_thr;
/* Big Data Field */
int capacity_now;
int data[CISD_DATA_MAX_PER_DAY];
int cable_data[CISD_CABLE_TYPE_MAX];
struct mutex padlock;
struct pad_data* pad_array;
unsigned int pad_count;
#if defined(CONFIG_QH_ALGORITHM)
unsigned long prev_time;
unsigned long qh_valid_time;
int prev_qh_value;
int prev_qh_vfsoc;
int qh_value_now;
int qh_vfsoc_now;
#endif
};
extern struct cisd *gcisd;
static inline void set_cisd_data(int type, int value)
{
if (gcisd && (type >= CISD_DATA_RESET_ALG && type < CISD_DATA_MAX_PER_DAY))
gcisd->data[type] = value;
}
static inline int get_cisd_data(int type)
{
if (!gcisd || (type < CISD_DATA_RESET_ALG || type >= CISD_DATA_MAX_PER_DAY))
return -1;
return gcisd->data[type];
}
static inline void increase_cisd_count(int type)
{
if (gcisd && (type >= CISD_DATA_RESET_ALG && type < CISD_DATA_MAX_PER_DAY))
gcisd->data[type]++;
}
void init_cisd_pad_data(struct cisd *cisd);
void count_cisd_pad_data(struct cisd *cisd, unsigned int pad_id);
#endif /* __SEC_CISD_H */

View File

@@ -0,0 +1,462 @@
/*
* sec_adc.c
* Samsung Mobile Battery Driver
*
* Copyright (C) 2012 Samsung Electronics
*
*
* 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.
*/
#include "include/sec_adc.h"
#define DEBUG
static struct qpnp_vadc_chip *adc_client;
struct adc_list {
const char* name;
struct iio_channel *channel;
struct qpnp_vadc_result prev_value;
//bool is_used;
};
static struct adc_list batt_adc_list[SEC_BAT_ADC_CHANNEL_NUM] = {
{.name = "adc-cable"},
{.name = "adc-bat"},
{.name = "adc-temp"},
{.name = "adc-temp"},
{.name = "adc-full"},
{.name = "adc-volt"},
{.name = "adc-chg-temp"},
{.name = "adc-in-bat"},
{.name = "adc-dischg"},
{.name = "adc-dischg-ntc"},
{.name = "adc-wpc-temp"},
{.name = "adc-slave-chg-temp"},
{.name = "adc-usb-temp"},
};
static void sec_bat_adc_ap_init(struct platform_device *pdev,
struct sec_battery_info *battery)
{
adc_client = qpnp_get_vadc(battery->dev, "sec-battery");
if (IS_ERR(adc_client)) {
int rc;
rc = PTR_ERR(adc_client);
if (rc != -EPROBE_DEFER)
pr_err("%s: Fail to get vadc %d\n", __func__, rc);
}
}
static void sec_bat_read_adc(struct qpnp_vadc_chip *vadc, int channel,
struct qpnp_vadc_result *result, int adc_channel)
{
int ret = 0;
int retry_cnt = RETRY_CNT;
do {
ret = qpnp_vadc_read(vadc, channel, result);
retry_cnt--;
} while ((retry_cnt > 0) && ret);
if (retry_cnt <= 0) {
pr_err("%s: Error in ADC(%d) retry_cnt(%d)\n", __func__, adc_channel, retry_cnt);
result->adc_code = batt_adc_list[adc_channel].prev_value.adc_code;
result->physical = batt_adc_list[adc_channel].prev_value.physical;
} else {
batt_adc_list[adc_channel].prev_value.adc_code = result->adc_code;
batt_adc_list[adc_channel].prev_value.physical = result->physical;
}
pr_debug("%s, channel: 0x%x, adc: %d, physical: %lld\n", __func__, channel, result->adc_code, result->physical);
}
static int sec_bat_adc_ap_read(struct sec_battery_info *battery, int channel)
{
struct qpnp_vadc_result results;
int data = -1;
switch (channel)
{
case SEC_BAT_ADC_CHANNEL_TEMP:
sec_bat_read_adc(adc_client, battery->pdata->temp_adc_channel, &results, channel);
data = results.adc_code;
break;
case SEC_BAT_ADC_CHANNEL_TEMP_AMBIENT:
data = 33000;
break;
case SEC_BAT_ADC_CHANNEL_BAT_CHECK:
sec_bat_read_adc(adc_client, battery->pdata->batt_channel, &results, channel);
data = results.adc_code;
break;
case SEC_BAT_ADC_CHANNEL_USB_TEMP:
sec_bat_read_adc(adc_client, battery->pdata->usb_temp_adc_channel, &results, channel);
data = results.adc_code;
break;
case SEC_BAT_ADC_CHANNEL_CHG_TEMP:
sec_bat_read_adc(adc_client, battery->pdata->chg_temp_adc_channel, &results, channel);
data = results.adc_code;
break;
case SEC_BAT_ADC_CHANNEL_WPC_TEMP:
sec_bat_read_adc(adc_client, battery->pdata->temp_adc_channel, &results, channel);
data = results.adc_code;
break;
case SEC_BAT_ADC_CHANNEL_INBAT_VOLTAGE:
sec_bat_read_adc(adc_client, VBAT_SNS, &results, channel);
data = ((int)results.physical)/1000;
break;
case SEC_BAT_ADC_CHANNEL_BLKT_TEMP:
sec_bat_read_adc(adc_client, battery->pdata->blkt_temp_adc_channel, &results, channel);
data = results.adc_code;
break;
default :
break;
}
return data;
}
static void sec_bat_adc_ap_exit(void)
{
return;
}
static void sec_bat_adc_none_init(struct platform_device *pdev)
{
}
static int sec_bat_adc_none_read(int channel)
{
return 0;
}
static void sec_bat_adc_none_exit(void)
{
}
static void sec_bat_adc_ic_init(struct platform_device *pdev)
{
}
static int sec_bat_adc_ic_read(int channel)
{
return 0;
}
static void sec_bat_adc_ic_exit(void)
{
}
static int adc_read_type(struct sec_battery_info *battery, int channel)
{
int adc = 0;
switch (battery->pdata->temp_adc_type)
{
case SEC_BATTERY_ADC_TYPE_NONE :
adc = sec_bat_adc_none_read(channel);
break;
case SEC_BATTERY_ADC_TYPE_AP :
adc = sec_bat_adc_ap_read(battery, channel);
break;
case SEC_BATTERY_ADC_TYPE_IC :
adc = sec_bat_adc_ic_read(channel);
break;
case SEC_BATTERY_ADC_TYPE_NUM :
break;
default :
break;
}
pr_debug("[%s] [%d] ADC = %d\n", __func__, channel, adc);
return adc;
}
static void adc_init_type(struct platform_device *pdev,
struct sec_battery_info *battery)
{
switch (battery->pdata->temp_adc_type)
{
case SEC_BATTERY_ADC_TYPE_NONE :
sec_bat_adc_none_init(pdev);
break;
case SEC_BATTERY_ADC_TYPE_AP :
sec_bat_adc_ap_init(pdev, battery);
break;
case SEC_BATTERY_ADC_TYPE_IC :
sec_bat_adc_ic_init(pdev);
break;
case SEC_BATTERY_ADC_TYPE_NUM :
break;
default :
break;
}
}
static void adc_exit_type(struct sec_battery_info *battery)
{
switch (battery->pdata->temp_adc_type)
{
case SEC_BATTERY_ADC_TYPE_NONE :
sec_bat_adc_none_exit();
break;
case SEC_BATTERY_ADC_TYPE_AP :
sec_bat_adc_ap_exit();
break;
case SEC_BATTERY_ADC_TYPE_IC :
sec_bat_adc_ic_exit();
break;
case SEC_BATTERY_ADC_TYPE_NUM :
break;
default :
break;
}
}
int sec_bat_get_adc_data(struct sec_battery_info *battery,
int adc_ch, int count)
{
int adc_data = 0;
int adc_max = 0;
int adc_min = 0;
int adc_total = 0;
int i = 0;
for (i = 0; i < count; i++) {
mutex_lock(&battery->adclock);
#ifdef CONFIG_OF
adc_data = adc_read_type(battery, adc_ch);
#else
adc_data = adc_read(battery->pdata, adc_ch);
#endif
mutex_unlock(&battery->adclock);
if (adc_data < 0)
goto err;
if (i != 0) {
if (adc_data > adc_max)
adc_max = adc_data;
else if (adc_data < adc_min)
adc_min = adc_data;
} else {
adc_max = adc_data;
adc_min = adc_data;
}
adc_total += adc_data;
}
return (adc_total - adc_max - adc_min) / (count - 2);
err:
return adc_data;
}
int adc_read(struct sec_battery_info *battery, int channel)
{
int adc = 0;
adc = adc_read_type(battery, channel);
dev_dbg(battery->dev, "[%s]adc = %d\n", __func__, adc);
return adc;
}
int sec_bat_get_adc_value(
struct sec_battery_info *battery, int channel)
{
int adc = 0;
adc = sec_bat_get_adc_data(battery, channel,
battery->pdata->adc_check_count);
if (adc < 0) {
dev_err(battery->dev,
"%s: Error in ADC\n", __func__);
return adc;
}
return adc;
}
int sec_bat_get_charger_type_adc
(struct sec_battery_info *battery)
{
/* It is true something valid is
connected to the device for charging.
By default this something is considered to be USB.*/
int result = SEC_BATTERY_CABLE_USB;
int adc = 0;
int i = 0;
/* Do NOT check cable type when cable_switch_check() returns false
* and keep current cable type
*/
if (battery->pdata->cable_switch_check &&
!battery->pdata->cable_switch_check())
return battery->cable_type;
adc = sec_bat_get_adc_value(battery,
SEC_BAT_ADC_CHANNEL_CABLE_CHECK);
/* Do NOT check cable type when cable_switch_normal() returns false
* and keep current cable type
*/
if (battery->pdata->cable_switch_normal &&
!battery->pdata->cable_switch_normal())
return battery->cable_type;
for (i = 0; i < SEC_BATTERY_CABLE_MAX; i++)
if ((adc > battery->pdata->cable_adc_value[i].min) &&
(adc < battery->pdata->cable_adc_value[i].max))
break;
if (i >= SEC_BATTERY_CABLE_MAX)
dev_err(battery->dev,
"%s : default USB\n", __func__);
else
result = i;
dev_dbg(battery->dev, "%s : result(%d), adc(%d)\n",
__func__, result, adc);
return result;
}
bool sec_bat_get_value_by_adc(
struct sec_battery_info *battery,
enum sec_battery_adc_channel channel,
union power_supply_propval *value)
{
int temp = 0;
int temp_adc;
int low = 0;
int high = 0;
int mid = 0;
const sec_bat_adc_table_data_t *temp_adc_table = {0 , };
unsigned int temp_adc_table_size = 0;
temp_adc = sec_bat_get_adc_value(battery, channel);
if (temp_adc < 0)
return true;
switch (channel) {
case SEC_BAT_ADC_CHANNEL_TEMP:
temp_adc_table = battery->pdata->temp_adc_table;
temp_adc_table_size =
battery->pdata->temp_adc_table_size;
battery->temp_adc = temp_adc;
break;
case SEC_BAT_ADC_CHANNEL_TEMP_AMBIENT:
temp_adc_table = battery->pdata->temp_amb_adc_table;
temp_adc_table_size =
battery->pdata->temp_amb_adc_table_size;
battery->temp_ambient_adc = temp_adc;
break;
case SEC_BAT_ADC_CHANNEL_USB_TEMP:
temp_adc_table = battery->pdata->usb_temp_adc_table;
temp_adc_table_size =
battery->pdata->usb_temp_adc_table_size;
battery->usb_temp_adc = temp_adc;
break;
case SEC_BAT_ADC_CHANNEL_CHG_TEMP:
temp_adc_table = battery->pdata->chg_temp_adc_table;
temp_adc_table_size =
battery->pdata->chg_temp_adc_table_size;
battery->chg_temp_adc = temp_adc;
break;
case SEC_BAT_ADC_CHANNEL_WPC_TEMP: /* Coil Therm */
temp_adc_table = battery->pdata->wpc_temp_adc_table;
temp_adc_table_size =
battery->pdata->wpc_temp_adc_table_size;
battery->wpc_temp_adc = temp_adc;
battery->coil_temp_adc = temp_adc;
break;
case SEC_BAT_ADC_CHANNEL_SLAVE_CHG_TEMP:
temp_adc_table = battery->pdata->slave_chg_temp_adc_table;
temp_adc_table_size =
battery->pdata->slave_chg_temp_adc_table_size;
battery->slave_chg_temp_adc = temp_adc;
break;
case SEC_BAT_ADC_CHANNEL_BLKT_TEMP:
temp_adc_table = battery->pdata->blkt_temp_adc_table;
temp_adc_table_size =
battery->pdata->blkt_temp_adc_table_size;
battery->blkt_temp_adc = temp_adc;
break;
default:
dev_err(battery->dev,
"%s: Invalid Property\n", __func__);
return false;
}
if (temp_adc_table[0].adc >= temp_adc) {
temp = temp_adc_table[0].data;
goto temp_by_adc_goto;
} else if (temp_adc_table[temp_adc_table_size-1].adc <= temp_adc) {
temp = temp_adc_table[temp_adc_table_size-1].data;
goto temp_by_adc_goto;
}
high = temp_adc_table_size - 1;
while (low <= high) {
mid = (low + high) / 2;
if (temp_adc_table[mid].adc > temp_adc)
high = mid - 1;
else if (temp_adc_table[mid].adc < temp_adc)
low = mid + 1;
else {
temp = temp_adc_table[mid].data;
goto temp_by_adc_goto;
}
}
temp = temp_adc_table[high].data;
temp += ((temp_adc_table[low].data - temp_adc_table[high].data) *
(temp_adc - temp_adc_table[high].adc)) /
(temp_adc_table[low].adc - temp_adc_table[high].adc);
temp_by_adc_goto:
value->intval = temp;
dev_info(battery->dev,
"%s:[%d] Temp(%d), Temp-ADC(%d)\n",
__func__,channel, temp, temp_adc);
return true;
}
bool sec_bat_check_vf_adc(struct sec_battery_info *battery)
{
int adc = 0;
adc = sec_bat_get_adc_data(battery,
SEC_BAT_ADC_CHANNEL_BAT_CHECK,
battery->pdata->adc_check_count);
if (adc < 0) {
dev_err(battery->dev, "%s: VF ADC error\n", __func__);
adc = battery->check_adc_value;
} else
battery->check_adc_value = adc;
dev_info(battery->dev, "%s: adc (%d)\n", __func__, battery->check_adc_value);
if ((battery->check_adc_value <= battery->pdata->check_adc_max) &&
(battery->check_adc_value >= battery->pdata->check_adc_min)) {
return true;
} else {
return false;
}
}
void adc_init(struct platform_device *pdev, struct sec_battery_info *battery)
{
adc_init_type(pdev, battery);
}
void adc_exit(struct sec_battery_info *battery)
{
adc_exit_type(battery);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,312 @@
/*
* sec_battery_ttf.c
* Samsung Mobile Battery Driver
*
* Copyright (C) 2019 Samsung Electronics
*
*
* 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.
*/
#include "include/sec_battery.h"
#include "include/sec_battery_ttf.h"
#if IS_ENABLED(CONFIG_CALC_TIME_TO_FULL)
int sec_calc_ttf(struct sec_battery_info *battery, unsigned int ttf_curr)
{
struct sec_cv_slope *cv_data = battery->ttf_d->cv_data;
int i, cc_time = 0, cv_time = 0;
int soc = battery->capacity;
int charge_current = ttf_curr;
int design_cap = battery->ttf_d->ttf_capacity;
union power_supply_propval value = {0, };
value.intval = SEC_FUELGAUGE_CAPACITY_TYPE_DYNAMIC_SCALE;
psy_do_property(battery->pdata->fuelgauge_name, get,
POWER_SUPPLY_PROP_CAPACITY, value);
soc = value.intval;
if (!cv_data || (ttf_curr <= 0)) {
pr_info("%s: no cv_data or val: %d\n", __func__, ttf_curr);
return -1;
}
for (i = 0; i < battery->ttf_d->cv_data_length; i++) {
if (charge_current >= cv_data[i].fg_current)
break;
}
i = i >= battery->ttf_d->cv_data_length ? battery->ttf_d->cv_data_length - 1 : i;
if (cv_data[i].soc < soc) {
for (i = 0; i < battery->ttf_d->cv_data_length; i++) {
if (soc <= cv_data[i].soc)
break;
}
cv_time =
((cv_data[i - 1].time - cv_data[i].time) * (cv_data[i].soc - soc)
/ (cv_data[i].soc - cv_data[i - 1].soc)) + cv_data[i].time;
} else { /* CC mode || NONE */
cv_time = cv_data[i].time;
cc_time =
design_cap * (cv_data[i].soc - soc) / ttf_curr * 3600 / 1000;
pr_debug("%s: cc_time: %d\n", __func__, cc_time);
if (cc_time < 0)
cc_time = 0;
}
pr_info("%s: cap: %d, soc: %4d, T: %6d, avg: %4d, cv soc: %4d, i: %4d, val: %d\n",
__func__, design_cap, soc, cv_time + cc_time,
battery->current_avg, cv_data[i].soc, i, ttf_curr);
if (cv_time + cc_time >= 0)
return cv_time + cc_time + 60;
else
return 60; /* minimum 1minutes */
}
void sec_bat_calc_time_to_full(struct sec_battery_info * battery)
{
if (delayed_work_pending(&battery->ttf_d->timetofull_work)) {
pr_info("%s: keep time_to_full(%5d sec)\n", __func__, battery->ttf_d->timetofull);
} else if (battery->status == POWER_SUPPLY_STATUS_CHARGING ||
(battery->status == POWER_SUPPLY_STATUS_FULL && battery->capacity != 100)) {
int charge = 0;
if (is_hv_wire_12v_type(battery->cable_type) ||
battery->max_charge_power >= (battery->ttf_d->pd_charging_charge_power + 5000)) { /* 20000mW */
charge = battery->ttf_d->ttf_hv_12v_charge_current;
} else if (is_hv_wire_type(battery->cable_type) || (battery->cable_type == SEC_BATTERY_CABLE_PREPARE_TA) ||
/* if max_charge_power could support over than max_charging_current, calculate based on ttf_hv_charge_current */
battery->max_charge_power >= (battery->ttf_d->max_charging_current * 5)) {
charge = battery->ttf_d->ttf_hv_charge_current;
} else if (is_hv_wireless_type(battery->cable_type) ||
battery->cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_HV) {
if (sec_bat_hv_wc_normal_mode_check(battery))
charge = battery->ttf_d->ttf_wireless_charge_current;
else
charge = battery->ttf_d->ttf_hv_wireless_charge_current;
} else if (is_nv_wireless_type(battery->cable_type)) {
charge = battery->ttf_d->ttf_wireless_charge_current;
#if defined(CONFIG_USE_POGO)
} else if (battery->cable_type == SEC_BATTERY_CABLE_POGO) {
charge = battery->ttf_d->ttf_pogo_charge_current;
#endif
} else {
charge = (battery->max_charge_power / 5) > battery->pdata->charging_current[battery->cable_type].fast_charging_current ?
battery->pdata->charging_current[battery->cable_type].fast_charging_current : (battery->max_charge_power / 5);
}
battery->ttf_d->timetofull = sec_calc_ttf(battery, charge);
dev_info(battery->dev, "%s: T: %5d sec, passed time: %5ld, current: %d\n",
__func__, battery->ttf_d->timetofull, battery->charging_passed_time, charge);
} else {
battery->ttf_d->timetofull = -1;
}
}
#ifdef CONFIG_OF
int sec_ttf_parse_dt(struct sec_battery_info *battery)
{
struct device_node *np;
struct sec_ttf_data *pdata = battery->ttf_d;
sec_battery_platform_data_t *bpdata = battery->pdata;
int ret = 0, len = 0;
const u32 *p;
pdata->pdev = battery;
np = of_find_node_by_name(NULL, "battery");
if (!np) {
pr_info("%s: np NULL\n", __func__);
return 1;
}
ret = of_property_read_u32(np, "battery,ttf_hv_12v_charge_current",
&pdata->ttf_hv_12v_charge_current);
if (ret) {
pdata->ttf_hv_12v_charge_current =
bpdata->charging_current[SEC_BATTERY_CABLE_12V_TA].fast_charging_current;
pr_info("%s: ttf_hv_12v_charge_current is Empty, Defualt value %d \n",
__func__, pdata->ttf_hv_12v_charge_current);
}
ret = of_property_read_u32(np, "battery,ttf_hv_charge_current",
&pdata->ttf_hv_charge_current);
if (ret) {
pdata->ttf_hv_charge_current =
bpdata->charging_current[SEC_BATTERY_CABLE_9V_TA].fast_charging_current;
pr_info("%s: ttf_hv_charge_current is Empty, Defualt value %d \n",
__func__, pdata->ttf_hv_charge_current);
}
ret = of_property_read_u32(np, "battery,ttf_hv_wireless_charge_current",
&pdata->ttf_hv_wireless_charge_current);
if (ret) {
pr_info("%s: ttf_hv_wireless_charge_current is Empty, Defualt value 0 \n", __func__);
pdata->ttf_hv_wireless_charge_current =
bpdata->charging_current[SEC_BATTERY_CABLE_HV_WIRELESS].fast_charging_current - 300;
}
ret = of_property_read_u32(np, "battery,ttf_wireless_charge_current",
&pdata->ttf_wireless_charge_current);
if (ret) {
pr_info("%s: ttf_wireless_charge_current is Empty, Defualt value 0 \n", __func__);
pdata->ttf_wireless_charge_current =
bpdata->charging_current[SEC_BATTERY_CABLE_WIRELESS].input_current_limit;
}
#if defined(CONFIG_USE_POGO)
ret = of_property_read_u32(np, "battery,ttf_pogo_charge_current",
&pdata->ttf_pogo_charge_current);
if (ret) {
pr_info("%s: ttf_pogo_charge_current is Empty, default value 0 \n", __func__);
pdata->ttf_pogo_charge_current =
bpdata->charging_current[SEC_BATTERY_CABLE_POGO].input_current_limit;
}
#endif
ret = of_property_read_u32(np, "battery,pd_charging_charge_power",
&pdata->pd_charging_charge_power);
if (ret) {
pr_err("%s: pd_charging_charge_power is Empty\n", __func__);
pdata->pd_charging_charge_power = 15000;
}
ret = of_property_read_u32(np, "battery,max_charging_current",
&pdata->max_charging_current);
if (ret) {
pr_err("%s: max_charging_current is Empty\n", __func__);
pdata->max_charging_current = 3000;
}
/* temporary dt setting */
ret = of_property_read_u32(np, "battery,ttf_predict_wc20_charge_current",
&pdata->ttf_predict_wc20_charge_current);
if (ret) {
pr_info("%s: ttf_predict_wc20_charge_current is Empty, Default value 0\n", __func__);
pdata->ttf_predict_wc20_charge_current =
bpdata->charging_current[SEC_BATTERY_CABLE_WIRELESS].input_current_limit;
}
ret = of_property_read_u32(np, "battery,ttf_dc25_charge_current",
&pdata->ttf_dc25_charge_current);
if (ret) {
pr_info("%s: ttf_dc25_charge_current is Empty, Default value 0 \n", __func__);
pdata->ttf_dc25_charge_current =
bpdata->charging_current[SEC_BATTERY_CABLE_9V_TA].fast_charging_current;
}
ret = of_property_read_u32(np, "battery,ttf_dc45_charge_current",
&pdata->ttf_dc45_charge_current);
if (ret) {
pr_info("%s: ttf_dc45_charge_current is Empty, Default value 0 \n", __func__);
pdata->ttf_dc45_charge_current = pdata->ttf_dc25_charge_current;
}
ret = of_property_read_u32(np, "battery,ttf_capacity",
&pdata->ttf_capacity);
if (ret < 0) {
pr_err("%s error reading capacity_calculation_type %d\n", __func__, ret);
pdata->ttf_capacity = bpdata->battery_full_capacity;
}
p = of_get_property(np, "battery,cv_data", &len);
if (p) {
pdata->cv_data = kzalloc(len, GFP_KERNEL);
pdata->cv_data_length = len / sizeof(struct sec_cv_slope);
pr_err("%s: len= %ld, length= %d, %d\n", __func__,
sizeof(int) * len, len, pdata->cv_data_length);
ret = of_property_read_u32_array(np, "battery,cv_data",
(u32 *)pdata->cv_data, len / sizeof(u32));
if (ret) {
pr_err("%s: failed to read battery->cv_data: %d\n",
__func__, ret);
kfree(pdata->cv_data);
pdata->cv_data = NULL;
}
} else {
pr_err("%s: there is not cv_data\n", __func__);
}
return 0;
}
#endif
void sec_bat_time_to_full_work(struct work_struct *work)
{
struct sec_ttf_data *dev = container_of(work,
struct sec_ttf_data, timetofull_work.work);
struct sec_battery_info *battery = dev->pdev;
union power_supply_propval value = {0, };
psy_do_property(battery->pdata->charger_name, get,
POWER_SUPPLY_PROP_CURRENT_MAX, value);
battery->current_max = value.intval;
value.intval = SEC_BATTERY_CURRENT_MA;
psy_do_property(battery->pdata->fuelgauge_name, get,
POWER_SUPPLY_PROP_CURRENT_NOW, value);
battery->current_now = value.intval;
value.intval = SEC_BATTERY_CURRENT_MA;
psy_do_property(battery->pdata->fuelgauge_name, get,
POWER_SUPPLY_PROP_CURRENT_AVG, value);
battery->current_avg = value.intval;
sec_bat_calc_time_to_full(battery);
dev_info(battery->dev, "%s:\n", __func__);
if (battery->voltage_now > 0)
battery->voltage_now--;
power_supply_changed(battery->psy_bat);
}
void ttf_work_start(struct sec_battery_info *battery)
{
if (lpcharge) {
cancel_delayed_work(&battery->ttf_d->timetofull_work);
if (battery->current_event & SEC_BAT_CURRENT_EVENT_AFC) {
int work_delay = 0;
if (!is_wireless_type(battery->cable_type)) {
work_delay = battery->pdata->pre_afc_work_delay;
} else {
work_delay = battery->pdata->pre_wc_afc_work_delay;
}
queue_delayed_work(battery->monitor_wqueue,
&battery->ttf_d->timetofull_work, msecs_to_jiffies(work_delay));
}
}
}
int ttf_display(struct sec_battery_info *battery)
{
if (battery->capacity == 100)
return 0;
if (((battery->status == POWER_SUPPLY_STATUS_CHARGING) ||
(battery->status == POWER_SUPPLY_STATUS_FULL && battery->capacity != 100)) &&
!battery->swelling_mode)
return battery->ttf_d->timetofull;
else
return 0;
}
void ttf_init(struct sec_battery_info *battery)
{
battery->ttf_d = kzalloc(sizeof(struct sec_ttf_data),
GFP_KERNEL);
if (!battery->ttf_d) {
pr_err("Failed to allocate memory\n");
}
sec_ttf_parse_dt(battery);
battery->ttf_d->timetofull = -1;
INIT_DELAYED_WORK(&battery->ttf_d->timetofull_work, sec_bat_time_to_full_work);
}
#else
int sec_calc_ttf(struct sec_battery_info *battery, unsigned int ttf_curr) { return -ENODEV; }
void sec_bat_calc_time_to_full(struct sec_battery_info *battery) { }
void sec_bat_time_to_full_work(struct work_struct *work) { }
void ttf_init(struct sec_battery_info *battery) { }
void ttf_work_start(struct sec_battery_info *battery) { }
int ttf_display(struct sec_battery_info *battery) { return 0; }
#ifdef CONFIG_OF
int sec_ttf_parse_dt(struct sec_battery_info *battery) { return -ENODEV; }
#endif
#endif

View File

@@ -0,0 +1,482 @@
/*
* sec_cisd.c
* Samsung Mobile Battery Driver
*
* Copyright (C) 2012 Samsung Electronics
*
*
* 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.
*/
#include "include/sec_battery.h"
#include "include/sec_cisd.h"
#if defined(CONFIG_SEC_ABC)
#include <linux/sti/abc_common.h>
#endif
const char* cisd_data_str[] ={
"RESET_ALG", "ALG_INDEX", "FULL_CNT", "CAP_MAX", "CAP_MIN", "RECHARGING_CNT", "VALERT_CNT",
"BATT_CYCLE", "WIRE_CNT", "WIRELESS_CNT", "HIGH_SWELLING_CNT", "LOW_SWELLING_CNT",
"SWELLING_CHARGING", "SWELLING_FULL_CNT", "SWELLING_RECOVERY_CNT", "AICL_CNT", "BATT_THM_MAX",
"BATT_THM_MIN", "CHG_THM_MAX", "CHG_THM_MIN", "WPC_THM_MAX", "WPC_THM_MIN", "USB_THM_MAX", "USB_THM_MIN",
"CHG_BATT_THM_MAX", "CHG_BATT_THM_MIN", "CHG_CHG_THM_MAX", "CHG_CHG_THM_MIN", "CHG_WPC_THM_MAX",
"CHG_WPC_THM_MIN", "CHG_USB_THM_MAX", "CHG_USB_THM_MIN", "USB_OVERHEAT_CHARGING", "UNSAFETY_VOLT",
"UNSAFETY_TEMP", "SAFETY_TIMER", "VSYS_OVP", "VBAT_OVP", "AFC_FAIL", "BUCK_OFF", "WATER_DET", "DROP_SENSOR"
};
const char* cisd_data_str_d[] = {
"FULL_CNT_D", "CAP_MAX_D", "CAP_MIN_D", "RECHARGING_CNT_D", "VALERT_CNT_D", "WIRE_CNT_D", "WIRELESS_CNT_D",
"HIGH_SWELLING_CNT_D", "LOW_SWELLING_CNT_D", "SWELLING_CHARGING_D", "SWELLING_FULL_CNT_D",
"SWELLING_RECOVERY_CNT_D", "AICL_CNT_D", "BATT_THM_MAX_D", "BATT_THM_MIN_D", "CHG_THM_MAX_D",
"CHG_THM_MIN_D", "WPC_THM_MAX_D", "WPC_THM_MIN_D", "USB_THM_MAX_D", "USB_THM_MIN_D",
"CHG_BATT_THM_MAX_D", "CHG_BATT_THM_MIN_D", "CHG_CHG_THM_MAX_D", "CHG_CHG_THM_MIN_D",
"CHG_WPC_THM_MAX_D", "CHG_WPC_THM_MIN_D", "CHG_USB_THM_MAX_D", "CHG_USB_THM_MIN_D",
"USB_OVERHEAT_CHARGING_D", "UNSAFETY_VOLT_D", "UNSAFETY_TEMP_D", "SAFETY_TIMER_D", "VSYS_OVP_D",
"VBAT_OVP_D", "AFC_FAIL_D", "BUCK_OFF_D", "WATER_DET_D", "DROP_SENSOR_D"
};
bool sec_bat_cisd_check(struct sec_battery_info *battery)
{
union power_supply_propval incur_val = {0, };
union power_supply_propval chgcur_val = {0, };
union power_supply_propval capcurr_val = {0, };
union power_supply_propval vbat_val = {0, };
struct cisd *pcisd = &battery->cisd;
bool ret = false;
if (battery->factory_mode || battery->is_jig_on || battery->skip_cisd) {
dev_dbg(battery->dev, "%s: No need to check in factory mode\n",
__func__);
return ret;
}
if ((battery->status == POWER_SUPPLY_STATUS_CHARGING) ||
(battery->status == POWER_SUPPLY_STATUS_FULL)) {
/* check abnormal vbat */
pcisd->ab_vbat_check_count = battery->voltage_now > pcisd->max_voltage_thr ?
pcisd->ab_vbat_check_count + 1 : 0;
if ((pcisd->ab_vbat_check_count >= pcisd->ab_vbat_max_count) &&
!(pcisd->state & CISD_STATE_OVER_VOLTAGE)) {
dev_info(battery->dev, "%s : [CISD] Battery Over Voltage Protction !! vbat(%d)mV\n",
__func__, battery->voltage_now);
vbat_val.intval = true;
psy_do_property("battery", set, (enum power_supply_property) POWER_SUPPLY_EXT_PROP_VBAT_OVP ,
vbat_val);
pcisd->data[CISD_DATA_VBAT_OVP]++;
pcisd->data[CISD_DATA_VBAT_OVP_PER_DAY]++;
pcisd->state |= CISD_STATE_OVER_VOLTAGE;
#if defined(CONFIG_SEC_ABC)
sec_abc_send_event("MODULE=battery@ERROR=over_voltage");
#endif
}
/* get actual input current */
psy_do_property(battery->pdata->charger_name, get,
POWER_SUPPLY_PROP_CURRENT_AVG, incur_val);
/* get actual charging current */
psy_do_property(battery->pdata->charger_name, get,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, chgcur_val);
if (battery->temperature > pcisd->data[CISD_DATA_CHG_BATT_TEMP_MAX])
pcisd->data[CISD_DATA_CHG_BATT_TEMP_MAX] = battery->temperature;
if (battery->temperature < pcisd->data[CISD_DATA_CHG_BATT_TEMP_MIN])
pcisd->data[CISD_DATA_CHG_BATT_TEMP_MIN] = battery->temperature;
if (battery->chg_temp > pcisd->data[CISD_DATA_CHG_CHG_TEMP_MAX])
pcisd->data[CISD_DATA_CHG_CHG_TEMP_MAX] = battery->chg_temp;
if (battery->chg_temp < pcisd->data[CISD_DATA_CHG_CHG_TEMP_MIN])
pcisd->data[CISD_DATA_CHG_CHG_TEMP_MIN] = battery->chg_temp;
if (battery->wpc_temp > pcisd->data[CISD_DATA_CHG_WPC_TEMP_MAX])
pcisd->data[CISD_DATA_CHG_WPC_TEMP_MAX] = battery->wpc_temp;
if (battery->wpc_temp < pcisd->data[CISD_DATA_CHG_WPC_TEMP_MIN])
pcisd->data[CISD_DATA_CHG_WPC_TEMP_MIN] = battery->wpc_temp;
if (battery->usb_temp > pcisd->data[CISD_DATA_CHG_USB_TEMP_MAX])
pcisd->data[CISD_DATA_CHG_USB_TEMP_MAX] = battery->usb_temp;
if (battery->usb_temp < pcisd->data[CISD_DATA_CHG_USB_TEMP_MIN])
pcisd->data[CISD_DATA_CHG_USB_TEMP_MIN] = battery->usb_temp;
if (battery->temperature > pcisd->data[CISD_DATA_CHG_BATT_TEMP_MAX_PER_DAY])
pcisd->data[CISD_DATA_CHG_BATT_TEMP_MAX_PER_DAY] = battery->temperature;
if (battery->temperature < pcisd->data[CISD_DATA_BATT_TEMP_MIN_PER_DAY])
pcisd->data[CISD_DATA_BATT_TEMP_MIN_PER_DAY] = battery->temperature;
if (battery->chg_temp > pcisd->data[CISD_DATA_CHG_CHG_TEMP_MAX_PER_DAY])
pcisd->data[CISD_DATA_CHG_CHG_TEMP_MAX_PER_DAY] = battery->chg_temp;
if (battery->chg_temp < pcisd->data[CISD_DATA_CHG_CHG_TEMP_MIN_PER_DAY])
pcisd->data[CISD_DATA_CHG_CHG_TEMP_MIN_PER_DAY] = battery->chg_temp;
if (battery->wpc_temp > pcisd->data[CISD_DATA_CHG_WPC_TEMP_MAX_PER_DAY])
pcisd->data[CISD_DATA_CHG_WPC_TEMP_MAX_PER_DAY] = battery->wpc_temp;
if (battery->wpc_temp < pcisd->data[CISD_DATA_CHG_WPC_TEMP_MIN_PER_DAY])
pcisd->data[CISD_DATA_CHG_WPC_TEMP_MIN_PER_DAY] = battery->wpc_temp;
if (battery->usb_temp > pcisd->data[CISD_DATA_CHG_USB_TEMP_MAX_PER_DAY])
pcisd->data[CISD_DATA_CHG_USB_TEMP_MAX_PER_DAY] = battery->usb_temp;
if (battery->usb_temp < pcisd->data[CISD_DATA_CHG_USB_TEMP_MIN_PER_DAY])
pcisd->data[CISD_DATA_CHG_USB_TEMP_MIN_PER_DAY] = battery->usb_temp;
if (battery->usb_temp > 800 && !battery->usb_overheat_check) {
battery->cisd.data[CISD_DATA_USB_OVERHEAT_CHARGING]++;
battery->cisd.data[CISD_DATA_USB_OVERHEAT_CHARGING_PER_DAY]++;
battery->usb_overheat_check = true;
}
dev_info(battery->dev, "%s: [CISD] iavg: %d, incur: %d, chgcur: %d,\n"
"cc_T: %ld, lcd_off_T: %ld, passed_T: %ld, full_T: %ld, chg_end_T: %ld, cisd: 0x%x\n",__func__,
battery->current_avg, incur_val.intval, chgcur_val.intval,
pcisd->cc_start_time, pcisd->lcd_off_start_time, battery->charging_passed_time,
battery->charging_fullcharged_time, pcisd->charging_end_time, pcisd->state);
} else {
/* discharging */
if (battery->status == POWER_SUPPLY_STATUS_NOT_CHARGING) {
/* check abnormal vbat */
pcisd->ab_vbat_check_count = battery->voltage_now > pcisd->max_voltage_thr ?
pcisd->ab_vbat_check_count + 1 : 0;
if ((pcisd->ab_vbat_check_count >= pcisd->ab_vbat_max_count) &&
!(pcisd->state & CISD_STATE_OVER_VOLTAGE)) {
pcisd->data[CISD_DATA_VBAT_OVP]++;
pcisd->data[CISD_DATA_VBAT_OVP_PER_DAY]++;
pcisd->state |= CISD_STATE_OVER_VOLTAGE;
#if defined(CONFIG_SEC_ABC)
sec_abc_send_event("MODULE=battery@ERROR=over_voltage");
#endif
}
}
capcurr_val.intval = SEC_BATTERY_CAPACITY_FULL;
psy_do_property(battery->pdata->fuelgauge_name, get,
POWER_SUPPLY_PROP_ENERGY_NOW, capcurr_val);
if (capcurr_val.intval == -1) {
dev_info(battery->dev, "%s: [CISD] FG I2C fail. skip cisd check \n", __func__);
return ret;
}
if (capcurr_val.intval > pcisd->data[CISD_DATA_CAP_MAX])
pcisd->data[CISD_DATA_CAP_MAX] = capcurr_val.intval;
if (capcurr_val.intval < pcisd->data[CISD_DATA_CAP_MIN])
pcisd->data[CISD_DATA_CAP_MIN] = capcurr_val.intval;
if (capcurr_val.intval > pcisd->data[CISD_DATA_CAP_MAX_PER_DAY])
pcisd->data[CISD_DATA_CAP_MAX_PER_DAY] = capcurr_val.intval;
if (capcurr_val.intval < pcisd->data[CISD_DATA_CAP_MIN_PER_DAY])
pcisd->data[CISD_DATA_CAP_MIN_PER_DAY] = capcurr_val.intval;
}
if (battery->temperature > pcisd->data[CISD_DATA_BATT_TEMP_MAX])
pcisd->data[CISD_DATA_BATT_TEMP_MAX] = battery->temperature;
if (battery->temperature < battery->cisd.data[CISD_DATA_BATT_TEMP_MIN])
pcisd->data[CISD_DATA_BATT_TEMP_MIN] = battery->temperature;
if (battery->chg_temp > pcisd->data[CISD_DATA_CHG_TEMP_MAX])
pcisd->data[CISD_DATA_CHG_TEMP_MAX] = battery->chg_temp;
if (battery->chg_temp < pcisd->data[CISD_DATA_CHG_TEMP_MIN])
pcisd->data[CISD_DATA_CHG_TEMP_MIN] = battery->chg_temp;
if (battery->wpc_temp > pcisd->data[CISD_DATA_WPC_TEMP_MAX])
pcisd->data[CISD_DATA_WPC_TEMP_MAX] = battery->wpc_temp;
if (battery->wpc_temp < battery->cisd.data[CISD_DATA_WPC_TEMP_MIN])
pcisd->data[CISD_DATA_WPC_TEMP_MIN] = battery->wpc_temp;
if (battery->usb_temp > pcisd->data[CISD_DATA_USB_TEMP_MAX])
pcisd->data[CISD_DATA_USB_TEMP_MAX] = battery->usb_temp;
if (battery->usb_temp < pcisd->data[CISD_DATA_USB_TEMP_MIN])
pcisd->data[CISD_DATA_USB_TEMP_MIN] = battery->usb_temp;
if (battery->temperature > pcisd->data[CISD_DATA_BATT_TEMP_MAX_PER_DAY])
pcisd->data[CISD_DATA_BATT_TEMP_MAX_PER_DAY] = battery->temperature;
if (battery->temperature < pcisd->data[CISD_DATA_BATT_TEMP_MIN_PER_DAY])
pcisd->data[CISD_DATA_BATT_TEMP_MIN_PER_DAY] = battery->temperature;
if (battery->chg_temp > pcisd->data[CISD_DATA_CHG_TEMP_MAX_PER_DAY])
pcisd->data[CISD_DATA_CHG_TEMP_MAX_PER_DAY] = battery->chg_temp;
if (battery->chg_temp < pcisd->data[CISD_DATA_CHG_TEMP_MIN_PER_DAY])
pcisd->data[CISD_DATA_CHG_TEMP_MIN_PER_DAY] = battery->chg_temp;
if (battery->wpc_temp > pcisd->data[CISD_DATA_WPC_TEMP_MAX_PER_DAY])
pcisd->data[CISD_DATA_WPC_TEMP_MAX_PER_DAY] = battery->wpc_temp;
if (battery->wpc_temp < pcisd->data[CISD_DATA_WPC_TEMP_MIN_PER_DAY])
pcisd->data[CISD_DATA_WPC_TEMP_MIN_PER_DAY] = battery->wpc_temp;
if (battery->usb_temp > pcisd->data[CISD_DATA_USB_TEMP_MAX_PER_DAY])
pcisd->data[CISD_DATA_USB_TEMP_MAX_PER_DAY] = battery->usb_temp;
if (battery->usb_temp < pcisd->data[CISD_DATA_USB_TEMP_MIN_PER_DAY])
pcisd->data[CISD_DATA_USB_TEMP_MIN_PER_DAY] = battery->usb_temp;
return ret;
}
struct cisd *gcisd;
void sec_battery_cisd_init(struct sec_battery_info *battery)
{
union power_supply_propval capfull_val;
battery->cisd.state = CISD_STATE_NONE;
battery->cisd.delay_time = 600; /* 10 min */
battery->cisd.diff_volt_now = 40;
battery->cisd.diff_cap_now = 5;
capfull_val.intval = SEC_BATTERY_CAPACITY_FULL;
psy_do_property(battery->pdata->fuelgauge_name, get,
POWER_SUPPLY_PROP_ENERGY_NOW, capfull_val);
battery->cisd.curr_cap_max = capfull_val.intval;
battery->cisd.err_cap_high_thr = battery->pdata->cisd_cap_high_thr;
battery->cisd.err_cap_low_thr = battery->pdata->cisd_cap_low_thr;
battery->cisd.cc_delay_time = 3600; /* 60 min */
battery->cisd.lcd_off_delay_time = 10200; /* 170 min */
battery->cisd.full_delay_time = 3600; /* 60 min */
battery->cisd.recharge_delay_time = 9000; /* 150 min */
battery->cisd.cc_start_time = 0;
battery->cisd.full_start_time = 0;
battery->cisd.lcd_off_start_time = 0;
battery->cisd.overflow_start_time = 0;
battery->cisd.charging_end_time = 0;
battery->cisd.charging_end_time_2 = 0;
battery->cisd.recharge_count = 0;
battery->cisd.recharge_count_2 = 0;
battery->cisd.recharge_count_thres = 2;
battery->cisd.leakage_e_time = 3600; /* 60 min */
battery->cisd.leakage_f_time = 7200; /* 120 min */
battery->cisd.leakage_g_time = 14400; /* 240 min */
battery->cisd.current_max_thres = 1600;
battery->cisd.charging_current_thres = 1000;
battery->cisd.current_avg_thres = 1000;
battery->cisd.data[CISD_DATA_ALG_INDEX] = battery->pdata->cisd_alg_index;
battery->cisd.data[CISD_DATA_FULL_COUNT] = 1;
battery->cisd.data[CISD_DATA_BATT_TEMP_MAX] = -300;
battery->cisd.data[CISD_DATA_CHG_TEMP_MAX] = -300;
battery->cisd.data[CISD_DATA_WPC_TEMP_MAX] = -300;
battery->cisd.data[CISD_DATA_BATT_TEMP_MIN] = 1000;
battery->cisd.data[CISD_DATA_CHG_TEMP_MIN] = 1000;
battery->cisd.data[CISD_DATA_WPC_TEMP_MIN] = 1000;
battery->cisd.data[CISD_DATA_CAP_MIN] = 0xFFFF;
battery->cisd.data[CISD_DATA_FULL_COUNT_PER_DAY] = 1;
battery->cisd.data[CISD_DATA_BATT_TEMP_MAX_PER_DAY] = -300;
battery->cisd.data[CISD_DATA_CHG_TEMP_MAX_PER_DAY] = -300;
battery->cisd.data[CISD_DATA_WPC_TEMP_MAX_PER_DAY] = -300;
battery->cisd.data[CISD_DATA_BATT_TEMP_MIN_PER_DAY] = 1000;
battery->cisd.data[CISD_DATA_CHG_TEMP_MIN_PER_DAY] = 1000;
battery->cisd.data[CISD_DATA_WPC_TEMP_MIN_PER_DAY] = 1000;
battery->cisd.data[CISD_DATA_CAP_MIN] = 0xFFFF;
battery->cisd.data[CISD_DATA_CHG_BATT_TEMP_MAX_PER_DAY] = -300;
battery->cisd.data[CISD_DATA_CHG_CHG_TEMP_MAX_PER_DAY] = -300;
battery->cisd.data[CISD_DATA_CHG_WPC_TEMP_MAX_PER_DAY] = -300;
battery->cisd.data[CISD_DATA_CHG_BATT_TEMP_MIN_PER_DAY] = 1000;
battery->cisd.data[CISD_DATA_CHG_CHG_TEMP_MIN_PER_DAY] = 1000;
battery->cisd.data[CISD_DATA_CHG_WPC_TEMP_MIN_PER_DAY] = 1000;
battery->cisd.capacity_now = capfull_val.intval;
battery->cisd.overflow_cap_thr = capfull_val.intval > battery->pdata->cisd_cap_limit ?
capfull_val.intval : battery->pdata->cisd_cap_limit;
battery->cisd.ab_vbat_max_count = 2; /* should be 1 */
battery->cisd.ab_vbat_check_count = 0;
battery->cisd.max_voltage_thr = battery->pdata->max_voltage_thr;
battery->cisd.cisd_alg_index = 6;
pr_info("%s: cisd.err_cap_high_thr:%d, cisd.err_cap_low_thr:%d, cisd.overflow_cap_thr:%d\n", __func__,
battery->cisd.err_cap_high_thr, battery->cisd.err_cap_low_thr, battery->cisd.overflow_cap_thr);
/* set cisd pointer */
gcisd = &battery->cisd;
/* initialize pad data */
mutex_init(&battery->cisd.padlock);
init_cisd_pad_data(&battery->cisd);
}
static struct pad_data* create_pad_data(unsigned int pad_id, unsigned int pad_count)
{
struct pad_data* temp_data;
temp_data = kzalloc(sizeof(struct pad_data), GFP_KERNEL);
if (temp_data == NULL)
return NULL;
temp_data->id = pad_id;
temp_data->count = pad_count;
temp_data->prev = temp_data->next = NULL;
return temp_data;
}
static struct pad_data* find_pad_data_by_id(struct cisd* cisd, unsigned int pad_id)
{
struct pad_data* temp_data = cisd->pad_array->next;
if (cisd->pad_count <= 0 || temp_data == NULL)
return NULL;
while ((temp_data->id != pad_id) &&
((temp_data = temp_data->next) != NULL));
return temp_data;
}
static void add_pad_data(struct cisd* cisd, unsigned int pad_id, unsigned int pad_count)
{
struct pad_data* temp_data = cisd->pad_array->next;
struct pad_data* pad_data;
if (pad_id == 0 || pad_id >= MAX_PAD_ID)
return;
pad_data = create_pad_data(pad_id, pad_count);
if (pad_data == NULL)
return;
pr_info("%s: id(0x%x), count(%d)\n", __func__, pad_id, pad_count);
while (temp_data) {
if (temp_data->id > pad_id) {
temp_data->prev->next = pad_data;
pad_data->prev = temp_data->prev;
pad_data->next = temp_data;
temp_data->prev = pad_data;
cisd->pad_count++;
return;
}
temp_data = temp_data->next;
}
pr_info("%s: failed to add pad_data(%d, %d)\n",
__func__, pad_id, pad_count);
kfree(pad_data);
}
void init_cisd_pad_data(struct cisd* cisd)
{
struct pad_data* temp_data = cisd->pad_array;
mutex_lock(&cisd->padlock);
while (temp_data) {
struct pad_data* next_data = temp_data->next;
kfree(temp_data);
temp_data = next_data;
}
/* create dummy data */
cisd->pad_count = 0;
cisd->pad_array = create_pad_data(0, 0);
if (cisd->pad_array == NULL)
return;
temp_data = create_pad_data(MAX_PAD_ID, 0);
if (temp_data == NULL) {
kfree(cisd->pad_array);
return;
}
cisd->pad_array->next = temp_data;
temp_data->prev = cisd->pad_array;
mutex_unlock(&cisd->padlock);
}
void count_cisd_pad_data(struct cisd* cisd, unsigned int pad_id)
{
struct pad_data* pad_data;
mutex_lock(&cisd->padlock);
if ((pad_data = find_pad_data_by_id(cisd, pad_id)) != NULL)
pad_data->count++;
else
add_pad_data(cisd, pad_id, 1);
mutex_unlock(&cisd->padlock);
}
static unsigned int convert_wc_index_to_pad_id(unsigned int wc_index)
{
switch (wc_index) {
case WC_SNGL_NOBLE:
return WC_PAD_ID_SNGL_NOBLE;
case WC_SNGL_VEHICLE:
return WC_PAD_ID_SNGL_VEHICLE;
case WC_SNGL_MINI:
return WC_PAD_ID_SNGL_MINI;
case WC_SNGL_ZERO:
return WC_PAD_ID_SNGL_ZERO;
case WC_SNGL_DREAM:
return WC_PAD_ID_SNGL_DREAM;
case WC_STAND_HERO:
return WC_PAD_ID_STAND_HERO;
case WC_STAND_DREAM:
return WC_PAD_ID_STAND_DREAM;
case WC_EXT_PACK:
return WC_PAD_ID_EXT_BATT_PACK;
case WC_EXT_PACK_TA:
return WC_PAD_ID_EXT_BATT_PACK_TA;
default:
break;
}
return 0;
}
void set_cisd_pad_data(struct sec_battery_info *battery, const char* buf)
{
struct cisd* pcisd = &battery->cisd;
unsigned int pad_index, pad_total_count, pad_id, pad_count;
struct pad_data* pad_data;
int i, x;
pr_info("%s: %s\n", __func__, buf);
if (sscanf(buf, "%10d%n", &pad_index, &x) <= 0) {
pr_info("%s: failed to read pad index\n", __func__);
return;
}
buf += (size_t)x;
pr_info("%s: stored pad_index(%d)\n", __func__, pad_index);
if (pcisd->pad_count > 0)
init_cisd_pad_data(pcisd);
if (!pad_index) {
for (i = WC_DATA_INDEX + 1; i < WC_DATA_MAX; i++) {
if (sscanf(buf, "%10d%n", &pad_count, &x) <= 0)
break;
buf += (size_t)x;
if (pad_count > 0) {
pad_id = convert_wc_index_to_pad_id(i);
mutex_lock(&pcisd->padlock);
if ((pad_data = find_pad_data_by_id(pcisd, pad_id)) != NULL)
pad_data->count = pad_count;
else
add_pad_data(pcisd, pad_id, pad_count);
mutex_unlock(&pcisd->padlock);
}
}
} else {
if ((sscanf(buf + 1, "%10d%n", &pad_total_count, &x) <= 0) ||
(pad_total_count >= MAX_PAD_ID))
return;
buf += (size_t)(x + 1);
pr_info("%s: add pad data(count: %d)\n", __func__, pad_total_count);
for (i = 0; i < pad_total_count; i++) {
if (sscanf(buf, " 0x%02x:%10d%n", &pad_id, &pad_count, &x) != 2) {
pr_info("%s: failed to read pad data(0x%x, %d, %d)!!!re-init pad data\n",
__func__, pad_id, pad_count, x);
init_cisd_pad_data(pcisd);
break;
}
buf += (size_t)x;
mutex_lock(&pcisd->padlock);
if ((pad_data = find_pad_data_by_id(pcisd, pad_id)) != NULL)
pad_data->count = pad_count;
else
add_pad_data(pcisd, pad_id, pad_count);
mutex_unlock(&pcisd->padlock);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,225 @@
/*
* drivers/battery/sm5705_charger_oper.c
*
* SM5705 Charger Operation Mode controller
*
* Copyright (C) 2015 Siliconmitus Technology Corp.
*
* 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; either version 2
* of the License, or (at your option) any later version.
*
*/
#include <linux/kernel.h>
#include "include/charger/sm5705_charger.h"
#include "include/charger/sm5705_charger_oper.h"
enum {
BST_OUT_4000mV = 0x0,
BST_OUT_4100mV = 0x1,
BST_OUT_4200mV = 0x2,
BST_OUT_4300mV = 0x3,
BST_OUT_4400mV = 0x4,
BST_OUT_4500mV = 0x5,
BST_OUT_4600mV = 0x6,
BST_OUT_4700mV = 0x7,
BST_OUT_4800mV = 0x8,
BST_OUT_4900mV = 0x9,
BST_OUT_5000mV = 0xA,
BST_OUT_5100mV = 0xB,
};
enum {
OTG_CURRENT_500mA = 0x0,
OTG_CURRENT_700mA = 0x1,
OTG_CURRENT_900mA = 0x2,
OTG_CURRENT_1500mA = 0x3,
};
#define SM5705_OPERATION_MODE_MASK 0x07
#define SM5705_BSTOUT_MASK 0x0F
#define SM5705_OTGCURRENT_MASK 0xC
struct sm5705_charger_oper_table_info {
unsigned char status;
unsigned char oper_mode;
unsigned char BST_OUT;
unsigned char OTG_CURRENT;
};
struct sm5705_charger_oper_info {
struct i2c_client *i2c;
bool suspend_mode;
int max_table_num;
struct sm5705_charger_oper_table_info current_table;
};
static struct sm5705_charger_oper_info oper_info;
/**
* (VBUS in/out) (WPC in/out) (FLASH on/off) (TORCH on/off) (OTG cable in/out) (Power Sharing cable in/out)
**/
static struct sm5705_charger_oper_table_info sm5705_charger_operation_mode_table[] = {
/* Charger mode : Charging ON */
{ make_OP_STATUS(0,0,0,0,0,0), SM5705_CHARGER_OP_MODE_CHG_ON, BST_OUT_4500mV, OTG_CURRENT_500mA},
{ make_OP_STATUS(1,0,0,0,0,0), SM5705_CHARGER_OP_MODE_CHG_ON, BST_OUT_4500mV, OTG_CURRENT_500mA},
{ make_OP_STATUS(1,1,0,0,0,0), SM5705_CHARGER_OP_MODE_CHG_ON, BST_OUT_4500mV, OTG_CURRENT_500mA},
{ make_OP_STATUS(1,0,0,1,0,0), SM5705_CHARGER_OP_MODE_CHG_ON, BST_OUT_4500mV, OTG_CURRENT_500mA},
{ make_OP_STATUS(1,1,0,1,0,0), SM5705_CHARGER_OP_MODE_CHG_ON, BST_OUT_4500mV, OTG_CURRENT_500mA},
{ make_OP_STATUS(0,1,0,0,0,0), SM5705_CHARGER_OP_MODE_CHG_ON, BST_OUT_4500mV, OTG_CURRENT_500mA},
{ make_OP_STATUS(1,0,0,0,1,0), SM5705_CHARGER_OP_MODE_CHG_ON, BST_OUT_4500mV, OTG_CURRENT_1500mA},
/* Charger mode : Flash Boost */
{ make_OP_STATUS(0,0,1,0,0,0), SM5705_CHARGER_OP_MODE_FLASH_BOOST, BST_OUT_4500mV, OTG_CURRENT_500mA},
{ make_OP_STATUS(0,0,1,0,1,0), SM5705_CHARGER_OP_MODE_FLASH_BOOST, BST_OUT_4500mV, OTG_CURRENT_1500mA},
{ make_OP_STATUS(0,0,1,0,0,1), SM5705_CHARGER_OP_MODE_FLASH_BOOST, BST_OUT_4500mV, OTG_CURRENT_900mA},
{ make_OP_STATUS(0,1,1,0,0,0), SM5705_CHARGER_OP_MODE_FLASH_BOOST, BST_OUT_4500mV, OTG_CURRENT_500mA},
{ make_OP_STATUS(0,1,1,0,1,0), SM5705_CHARGER_OP_MODE_FLASH_BOOST, BST_OUT_4500mV, OTG_CURRENT_1500mA},
{ make_OP_STATUS(0,1,1,0,0,1), SM5705_CHARGER_OP_MODE_FLASH_BOOST, BST_OUT_4500mV, OTG_CURRENT_900mA},
{ make_OP_STATUS(1,0,1,0,0,0), SM5705_CHARGER_OP_MODE_FLASH_BOOST, BST_OUT_4500mV, OTG_CURRENT_500mA},
{ make_OP_STATUS(1,1,1,0,0,0), SM5705_CHARGER_OP_MODE_FLASH_BOOST, BST_OUT_4500mV, OTG_CURRENT_500mA},
{ make_OP_STATUS(0,0,0,1,0,0), SM5705_CHARGER_OP_MODE_FLASH_BOOST, BST_OUT_4500mV, OTG_CURRENT_500mA},
/* Charger mode : Wireless OTG & Charger ON */
{ make_OP_STATUS(0,1,0,1,1,0), SM5705_CHARGER_OP_MODE_WPC_OTG_CHG_ON, BST_OUT_5100mV, OTG_CURRENT_1500mA},
{ make_OP_STATUS(0,1,0,1,0,1), SM5705_CHARGER_OP_MODE_WPC_OTG_CHG_ON, BST_OUT_5100mV, OTG_CURRENT_900mA},
{ make_OP_STATUS(0,1,0,1,0,0), SM5705_CHARGER_OP_MODE_WPC_OTG_CHG_ON, BST_OUT_4500mV, OTG_CURRENT_500mA},
{ make_OP_STATUS(0,1,0,0,1,0), SM5705_CHARGER_OP_MODE_WPC_OTG_CHG_ON, BST_OUT_5100mV, OTG_CURRENT_1500mA},
{ make_OP_STATUS(0,1,0,0,0,1), SM5705_CHARGER_OP_MODE_WPC_OTG_CHG_ON, BST_OUT_5100mV, OTG_CURRENT_900mA},
/* Charger mode : USB OTG */
{ make_OP_STATUS(0,0,0,1,1,0), SM5705_CHARGER_OP_MODE_USB_OTG, BST_OUT_5100mV, OTG_CURRENT_1500mA},
{ make_OP_STATUS(0,0,0,1,0,1), SM5705_CHARGER_OP_MODE_USB_OTG, BST_OUT_5100mV, OTG_CURRENT_900mA},
{ make_OP_STATUS(0,0,0,0,1,0), SM5705_CHARGER_OP_MODE_USB_OTG, BST_OUT_5100mV, OTG_CURRENT_1500mA},
{ make_OP_STATUS(0,0,0,0,0,1), SM5705_CHARGER_OP_MODE_USB_OTG, BST_OUT_5100mV, OTG_CURRENT_900mA},
};
/**
* SM5705 Charger operation mode controller relative I2C setup
*/
static int sm5705_charger_oper_set_mode(struct i2c_client *i2c, unsigned char mode)
{
return sm5705_update_reg(i2c, SM5705_REG_CNTL, mode, SM5705_OPERATION_MODE_MASK);
}
static int sm5705_charger_oper_set_BSTOUT(struct i2c_client *i2c, unsigned char BSTOUT)
{
return sm5705_update_reg(i2c, SM5705_REG_FLEDCNTL6, BSTOUT, SM5705_BSTOUT_MASK);
}
static int sm5705_charger_oper_set_OTG_CURRENT(struct i2c_client *i2c, unsigned char OTG_CURRENT)
{
return sm5705_update_reg(i2c, SM5705_REG_CHGCNTL6, OTG_CURRENT << 2, SM5705_OTGCURRENT_MASK);
}
/**
* SM5705 Charger operation mode controller API functions.
*/
static inline unsigned char _update_status(int event_type, bool enable)
{
if (event_type > SM5705_CHARGER_OP_EVENT_VBUS)
return oper_info.current_table.status;
if (enable)
return (oper_info.current_table.status | (1 << event_type));
else
return (oper_info.current_table.status & ~(1 << event_type));
}
static inline void sm5705_charger_oper_change_state(unsigned char new_status)
{
int i;
for (i=0; i < oper_info.max_table_num; ++i) {
if (new_status == sm5705_charger_operation_mode_table[i].status)
break;
}
if (i == oper_info.max_table_num) {
pr_err("sm5705-charger: %s: can't find matched Charger Operation Mode Table (status = 0x%x)\n", __func__, new_status);
return;
}
if (oper_info.suspend_mode) {
pr_info("sm5705-charger: %s: skip setting by suspend mode(MODE: %d)\n",
__func__, oper_info.current_table.oper_mode);
goto skip_oper_change_state;
}
if (sm5705_charger_operation_mode_table[i].BST_OUT != oper_info.current_table.BST_OUT) {
sm5705_charger_oper_set_BSTOUT(oper_info.i2c, sm5705_charger_operation_mode_table[i].BST_OUT);
oper_info.current_table.BST_OUT = sm5705_charger_operation_mode_table[i].BST_OUT;
}
if (sm5705_charger_operation_mode_table[i].OTG_CURRENT != oper_info.current_table.OTG_CURRENT) {
sm5705_charger_oper_set_OTG_CURRENT(oper_info.i2c, sm5705_charger_operation_mode_table[i].OTG_CURRENT);
oper_info.current_table.OTG_CURRENT = sm5705_charger_operation_mode_table[i].OTG_CURRENT;
}
if (sm5705_call_fg_device_id() < 5) {
/* USB_OTG to CHG_ON work-around for BAT_REG stabilize */
if (oper_info.current_table.oper_mode == SM5705_CHARGER_OP_MODE_USB_OTG && \
sm5705_charger_operation_mode_table[i].oper_mode == SM5705_CHARGER_OP_MODE_CHG_ON) {
pr_info("sm5705-charger: %s: trans op_mode:suspend for BAT_REG stabilize (time=100ms)\n", __func__);
sm5705_charger_oper_set_mode(oper_info.i2c, SM5705_CHARGER_OP_MODE_SUSPEND);
msleep(100);
}
}
if (sm5705_charger_operation_mode_table[i].oper_mode != oper_info.current_table.oper_mode) {
sm5705_charger_oper_set_mode(oper_info.i2c, sm5705_charger_operation_mode_table[i].oper_mode);
oper_info.current_table.oper_mode = sm5705_charger_operation_mode_table[i].oper_mode;
}
skip_oper_change_state:
oper_info.current_table.status = new_status;
pr_info("sm5705-charger: %s: New table[%d] info (STATUS: 0x%x, MODE: %d, BST_OUT: 0x%x, OTG_CURRENT: 0x%x\n", \
__func__, i, oper_info.current_table.status, oper_info.current_table.oper_mode, oper_info.current_table.BST_OUT, oper_info.current_table.OTG_CURRENT);
}
int sm5705_charger_oper_push_event(int event_type, bool enable)
{
unsigned char new_status;
if (oper_info.i2c == NULL) {
pr_err("sm5705-charger: %s: required sm5705 charger operation table initialize\n", __func__);
return -ENOENT;
}
pr_info("sm5705-charger: %s: event_type=%d, enable=%d\n", __func__, event_type, enable);
if (event_type == SM5705_CHARGER_OP_EVENT_SUSPEND_MODE) {
oper_info.suspend_mode = enable;
if (oper_info.suspend_mode) {
oper_info.current_table.oper_mode = SM5705_CHARGER_OP_MODE_SUSPEND;
sm5705_charger_oper_set_mode(oper_info.i2c, SM5705_CHARGER_OP_MODE_SUSPEND);
} else if (oper_info.current_table.oper_mode == SM5705_CHARGER_OP_MODE_SUSPEND)
sm5705_charger_oper_change_state(oper_info.current_table.status);
goto out;
}
new_status = _update_status(event_type, enable);
if (new_status == oper_info.current_table.status)
goto out;
sm5705_charger_oper_change_state(new_status);
out:
return 0;
}
EXPORT_SYMBOL(sm5705_charger_oper_push_event);
int sm5705_charger_oper_table_init(struct i2c_client *i2c)
{
if (i2c == NULL) {
pr_err("sm5705-charger: %s: invalid i2c client handler=n", __func__);
return -EINVAL;
}
oper_info.i2c = i2c;
/* set default operation mode condition */
oper_info.suspend_mode = false;
oper_info.max_table_num = ARRAY_SIZE(sm5705_charger_operation_mode_table);
oper_info.current_table.status = make_OP_STATUS(0, 0, 0, 0, 0, 0);
oper_info.current_table.oper_mode = SM5705_CHARGER_OP_MODE_CHG_ON;
oper_info.current_table.BST_OUT = BST_OUT_4500mV;
oper_info.current_table.OTG_CURRENT = OTG_CURRENT_500mA;
sm5705_charger_oper_set_mode(oper_info.i2c, oper_info.current_table.oper_mode);
sm5705_charger_oper_set_BSTOUT(oper_info.i2c, oper_info.current_table.BST_OUT);
sm5705_charger_oper_set_OTG_CURRENT(oper_info.i2c, oper_info.current_table.OTG_CURRENT);
pr_info("sm5705-charger: %s: current table info (STATUS: 0x%x, MODE: %d, BST_OUT: 0x%x, OTG_CURRENT: 0x%x\n", \
__func__, oper_info.current_table.status, oper_info.current_table.oper_mode, oper_info.current_table.BST_OUT, oper_info.current_table.OTG_CURRENT);
return 0;
}
EXPORT_SYMBOL(sm5705_charger_oper_table_init);
int sm5705_charger_oper_get_current_status(void)
{
return oper_info.current_table.status;
}
EXPORT_SYMBOL(sm5705_charger_oper_get_current_status);
int sm5705_charger_oper_get_current_op_mode(void)
{
return oper_info.current_table.oper_mode;
}
EXPORT_SYMBOL(sm5705_charger_oper_get_current_op_mode);

File diff suppressed because it is too large Load Diff

61
drivers/ccic/Kconfig Normal file
View File

@@ -0,0 +1,61 @@
#
# CCIC devices
#
comment "CCIC configs"
config CCIC_NOTIFIER
bool "CCIC notifier support"
depends on I2C
default n
help
If you say yes here you will get support for
the CCIC attached device status change notification.
config CCIC_S2MM005
bool "CCIC S2MM005"
depends on I2C
default n
help
If you say yes here you will get support for
s2mm005 ccic full version chipset
config CCIC_ALTERNATE_MODE
bool "support CCIC alternate mode"
depends on I2C
default n
help
If you say yes here you will get support for
alternate mode
config CCIC_LPM_ENABLE
bool "Support LPM ENABLE"
depends on CCIC_S2MM005
default n
help
If you say yes here you will get support for
lpm enable
config CCIC_WATER_DETECT
bool "support WATER DETECT"
depends on CCIC_S2MM005
default n
help
If you say yes here you will get support for
water detect Enable
config CCIC_MANUAL_UPDATE
bool "support CCIC manual update"
depends on CCIC_S2MM005
default n
help
If you say yes here you will get support for
ccic manual update
config CCIC_S2MM005_ANALOG_AUDIO
bool "Support type-C analog audio"
depends on CCIC_S2MM005
default n
help
If you say yes here you will get support for
water detect Enable

7
drivers/ccic/Makefile Normal file
View File

@@ -0,0 +1,7 @@
#
# Makefile for ccic devices
#
obj-$(CONFIG_CCIC_NOTIFIER) += ccic_notifier.o ccic_sysfs.o ccic_alternate.o
obj-$(CONFIG_CCIC_S2MM005) += s2mm005_fw.o s2mm005_cc.o s2mm005_pd.o s2mm005.o
obj-$(CONFIG_CCIC_ALTERNATE_MODE) += ccic_misc.o

File diff suppressed because it is too large Load Diff

253
drivers/ccic/ccic_misc.c Normal file
View File

@@ -0,0 +1,253 @@
/*
* driver/ccic/ccic_misc.c - S2MM005 CCIC MISC driver
*
* Copyright (C) 2017 Samsung Electronics
* Author: Wookwang Lee <wookwang.lee@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*
*/
//serial_acm.c
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/device.h>
#include <linux/poll.h>
#include "ccic_misc.h"
static struct ccic_misc_dev *c_dev;
#define MAX_BUF 255
#define DEXDOCK_PRODUCT_ID 0xA020
#define NODE_OF_MISC "ccic_misc"
#define CCIC_IOCTL_UVDM _IOWR('C', 0, struct uvdm_data)
static inline int _lock(atomic_t *excl)
{
if (atomic_inc_return(excl) == 1) {
return 0;
} else {
atomic_dec(excl);
return -1;
}
}
static inline void _unlock(atomic_t *excl)
{
atomic_dec(excl);
}
static int ccic_misc_open(struct inode *inode, struct file *file)
{
int ret = 0;
pr_info("%s + open success\n", __func__);
if (!c_dev) {
pr_err("%s - error : c_dev is NULL\n", __func__);
ret = -ENODEV;
goto err;
}
if (_lock(&c_dev->open_excl)) {
pr_err("%s - error : device busy\n", __func__);
ret = -EBUSY;
goto err1;
}
if (!samsung_uvdm_ready()) {
// check if there is some connection
_unlock(&c_dev->open_excl);
pr_err("%s - error : uvdm is not ready\n", __func__);
ret = -EBUSY;
goto err1;
}
pr_info("%s - open success\n", __func__);
return 0;
err1:
err:
return ret;
}
static int ccic_misc_close(struct inode *inode, struct file *file)
{
if (c_dev)
_unlock(&c_dev->open_excl);
samsung_uvdm_close();
pr_info("%s - close success\n", __func__);
return 0;
}
static int send_uvdm_message(void *data, int size)
{
int ret;
pr_info("%s - size : %d\n", __func__, size);
ret = samsung_uvdm_out_request_message(data, size);
return ret;
}
static int receive_uvdm_message(void *data, int size)
{
int ret;
pr_info("%s - size : %d\n", __func__, size);
ret = samsung_uvdm_in_request_message(data);
return ret;
}
static long
ccic_misc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
void *buf = NULL;
if (_lock(&c_dev->ioctl_excl)) {
pr_err("%s - error : ioctl busy - cmd : %d\n", __func__, cmd);
ret = -EBUSY;
goto err2;
}
switch (cmd) {
case CCIC_IOCTL_UVDM:
pr_info("%s - CCIC_IOCTL_UVDM cmd\n", __func__);
if (copy_from_user(&c_dev->u_data, (void __user *) arg,
sizeof(struct uvdm_data))) {
ret = -EIO;
pr_err("%s - copy_from_user error\n", __func__);
goto err1;
}
buf = kzalloc(MAX_BUF, GFP_KERNEL);
if (!buf) {
ret = -EINVAL;
pr_err("%s - kzalloc error\n", __func__);
goto err1;
}
if (c_dev->u_data.size > MAX_BUF) {
ret = -ENOMEM;
pr_err("%s - user data size is %d error\n", __func__, c_dev->u_data.size);
goto err;
}
if (c_dev->u_data.dir == DIR_OUT) {
if (copy_from_user(buf, c_dev->u_data.pData,\
c_dev->u_data.size)) {
ret = -EIO;
pr_err("%s - copy_from_user error\n", __func__);
goto err;
}
ret = send_uvdm_message(buf, c_dev->u_data.size);
if (ret <= 0) {
pr_err("%s - send_uvdm_message error\n", __func__);
goto err;
}
} else {
ret = receive_uvdm_message(buf, c_dev->u_data.size);
if (ret <= 0) {
pr_err("%s - receive_uvdm_message error\n", __func__);
goto err;
}
if (copy_to_user((void __user *)c_dev->u_data.pData,
buf, ret)) {
ret = -EIO;
pr_err("%s - copy_to_user error\n", __func__);
goto err;
}
}
break;
default:
pr_err("%s - unknown ioctl cmd : %d\n", __func__, cmd);
ret = -ENOIOCTLCMD;
goto err;
}
err:
kfree(buf);
err1:
_unlock(&c_dev->ioctl_excl);
err2:
return ret;
}
#ifdef CONFIG_COMPAT
static long
ccic_misc_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
pr_info("%s - cmd : %d\n", __func__, cmd);
ret = ccic_misc_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
return ret;
}
#endif
static const struct file_operations ccic_misc_fops = {
.owner = THIS_MODULE,
.open = ccic_misc_open,
.release = ccic_misc_close,
.llseek = no_llseek,
.unlocked_ioctl = ccic_misc_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = ccic_misc_compat_ioctl,
#endif
};
static struct miscdevice ccic_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = NODE_OF_MISC,
.fops = &ccic_misc_fops,
};
int ccic_misc_init(void)
{
int ret = 0;
ret = misc_register(&ccic_misc_device);
if (ret) {
pr_err("%s - return error : %d\n", __func__, ret);
goto err;
}
c_dev = kzalloc(sizeof(struct ccic_misc_dev), GFP_KERNEL);
if (!c_dev) {
ret = -ENOMEM;
pr_err("%s - kzalloc failed : %d\n", __func__, ret);
goto err1;
}
atomic_set(&c_dev->open_excl, 0);
atomic_set(&c_dev->ioctl_excl, 0);
pr_info("%s - register success\n", __func__);
return 0;
err1:
misc_deregister(&ccic_misc_device);
err:
return ret;
}
EXPORT_SYMBOL(ccic_misc_init);
void ccic_misc_exit(void)
{
pr_info("%s() called\n", __func__);
if (!c_dev)
return;
kfree(c_dev);
misc_deregister(&ccic_misc_device);
}
EXPORT_SYMBOL(ccic_misc_exit);

105
drivers/ccic/ccic_misc.h Normal file
View File

@@ -0,0 +1,105 @@
/*
* driver/ccic/ccic_misc.h - S2MM005 CCIC MISC driver
*
* Copyright (C) 2017 Samsung Electronics
* Author: Wookwang Lee <wookwang.lee@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*
*/
enum uvdm_data_type {
TYPE_SHORT = 0,
TYPE_LONG,
};
enum uvdm_direction_type {
DIR_OUT = 0,
DIR_IN,
};
#if 0
typedef union sec_uvdm_header {
uint32_t data;
struct {
uint8_t bdata[4];
} BYTES;
struct {
uint32_t data:8,
total_number_of_uvdm_set:4,
direction:1,
command_type:2,
data_type:1,
pid:16;
} BITS;
} U_SEC_UVDM_HEADER;
typedef U_SEC_UVDM_HEADER U_SEC_UVDM_RESPONSE_HEADER;
typedef union sec_tx_data_header {
uint32_t data;
struct {
uint8_t bdata[4];
} BYTES;
struct {
uint32_t data_size_of_current_set:8,
total_data_size:8,
reserved:12,
order_of_current_uvdm_set:4;
} BITS;
} U_SEC_TX_DATA_HEADER;
typedef union sec_data_tx_tailer {
uint32_t data;
struct {
uint8_t bdata[4];
} BYTES;
struct {
uint32_t checksum:16,
reserved:16;
} BITS;
} U_SEC_DATA_TX_TAILER;
typedef union sec_data_rx_header {
uint32_t data;
struct {
uint8_t bdata[4];
} BYTES;
struct {
uint32_t reserved:18,
result_value:2,
received_data_size_of_current_set:8,
order_of_current_uvdm_set:4;
} BITS;
} U_SEC_DATA_RX_HEADER;
#endif
struct uvdm_data {
unsigned short pid; /* Product ID */
char type; /* uvdm_data_type */
char dir; /* uvdm_direction_type */
unsigned int size; /* data size */
void __user *pData; /* data pointer */
};
struct ccic_misc_dev {
struct uvdm_data u_data;
atomic_t open_excl;
atomic_t ioctl_excl;
int (*uvdm_write)(void *data, int size);
int (*uvdm_read)(void *data, int size);
};
extern ssize_t samsung_uvdm_out_request_message(void *data, size_t size);
extern int samsung_uvdm_in_request_message(void *data);
extern int samsung_uvdm_ready(void);
extern void samsung_uvdm_close(void);

View File

@@ -0,0 +1,321 @@
#include <linux/device.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/ccic/ccic_notifier.h>
#include <linux/sec_class.h>
#include <linux/ccic/ccic_sysfs.h>
#ifdef CONFIG_USB_TYPEC_MANAGER_NOTIFIER
#include <linux/battery/battery_notifier.h>
#endif
#include <linux/usb_notify.h>
#define DEBUG
#define SET_CCIC_NOTIFIER_BLOCK(nb, fn, dev) do { \
(nb)->notifier_call = (fn); \
(nb)->priority = (dev); \
} while (0)
#define DESTROY_CCIC_NOTIFIER_BLOCK(nb) \
SET_CCIC_NOTIFIER_BLOCK(nb, NULL, -1)
static struct ccic_notifier_struct ccic_notifier;
struct device *ccic_device;
static int ccic_notifier_init_done = 0;
int ccic_notifier_init(void);
char CCIC_NOTI_DEST_Print[CCIC_NOTI_DEST_NUM][10] =
{
{"INITIAL"},
{"USB"},
{"BATTERY"},
{"PDIC"},
{"MUIC"},
{"CCIC"},
{"MANAGER"},
{"DP"},
{"DPUSB"},
{"ALL"},
};
char CCIC_NOTI_ID_Print[CCIC_NOTI_ID_NUM][20] =
{
{"ID_INITIAL"},
{"ID_ATTACH"},
{"ID_RID"},
{"ID_USB"},
{"ID_POWER_STATUS"},
{"ID_WATER"},
{"ID_VCONN"},
{"ID_DP_CONNECT"},
{"ID_DP_HPD"},
{"ID_DP_LINK_CONF"},
{"ID_DP_USB"},
{"ID_ROLE_SWAP"},
{"ID_FAC"},
{"ID_PIN_STATUS"},
};
char CCIC_NOTI_RID_Print[CCIC_NOTI_RID_NUM][15] =
{
{"RID_UNDEFINED"},
{"RID_000K"},
{"RID_001K"},
{"RID_255K"},
{"RID_301K"},
{"RID_523K"},
{"RID_619K"},
{"RID_OPEN"},
};
char CCIC_NOTI_USB_STATUS_Print[CCIC_NOTI_USB_STATUS_NUM][20] =
{
{"USB_DETACH"},
{"USB_ATTACH_DFP"},
{"USB_ATTACH_UFP"},
{"USB_ATTACH_DRP"},
{"USB_ATTACH_NO_USB"},
};
int ccic_notifier_register(struct notifier_block *nb, notifier_fn_t notifier,
ccic_notifier_device_t listener)
{
int ret = 0;
pr_info("%s: listener=%d register\n", __func__, listener);
/* Check if CCIC Notifier is ready. */
if(!ccic_notifier_init_done)
ccic_notifier_init();
if (!ccic_device) {
pr_err("%s: Not Initialized...\n", __func__);
return -1;
}
SET_CCIC_NOTIFIER_BLOCK(nb, notifier, listener);
ret = blocking_notifier_chain_register(&(ccic_notifier.notifier_call_chain), nb);
if (ret < 0)
pr_err("%s: blocking_notifier_chain_register error(%d)\n",
__func__, ret);
/* current ccic's attached_device status notify */
nb->notifier_call(nb, 0,
&(ccic_notifier.ccic_template));
return ret;
}
int ccic_notifier_unregister(struct notifier_block *nb)
{
int ret = 0;
pr_info("%s: listener=%d unregister\n", __func__, nb->priority);
ret = blocking_notifier_chain_unregister(&(ccic_notifier.notifier_call_chain), nb);
if (ret < 0)
pr_err("%s: blocking_notifier_chain_unregister error(%d)\n",
__func__, ret);
DESTROY_CCIC_NOTIFIER_BLOCK(nb);
return ret;
}
static void ccic_uevent_work(int id, int state)
{
char *water[2] = { "CCIC=WATER", NULL };
char *dry[2] = { "CCIC=DRY", NULL };
char *vconn[2] = { "CCIC=VCONN", NULL };
#if defined(CONFIG_SEC_FACTORY)
char ccicrid[15] = {0,};
char *rid[2] = {ccicrid, NULL};
char ccicFacErr[20] = {0,};
char *facErr[2] = {ccicFacErr, NULL};
#endif
pr_info("usb: %s: id=%s state=%d\n", __func__, CCIC_NOTI_ID_Print[id], state);
switch (id) {
case CCIC_NOTIFY_ID_WATER:
if (state)
kobject_uevent_env(&ccic_device->kobj, KOBJ_CHANGE, water);
else
kobject_uevent_env(&ccic_device->kobj, KOBJ_CHANGE, dry);
break;
case CCIC_NOTIFY_ID_VCONN:
kobject_uevent_env(&ccic_device->kobj, KOBJ_CHANGE, vconn);
break;
#if defined(CONFIG_SEC_FACTORY)
case CCIC_NOTIFY_ID_RID:
snprintf(ccicrid, sizeof(ccicrid), "%s",
(state < CCIC_NOTI_RID_NUM) ? CCIC_NOTI_RID_Print[state] : CCIC_NOTI_RID_Print[0]);
kobject_uevent_env(&ccic_device->kobj, KOBJ_CHANGE, rid);
break;
case CCIC_NOTIFY_ID_FAC:
snprintf(ccicFacErr, sizeof(ccicFacErr), "%s:%d",
"ERR_STATE", state);
kobject_uevent_env(&ccic_device->kobj, KOBJ_CHANGE, facErr);
break;
#endif
default:
break;
}
}
/* ccic's attached_device attach broadcast */
int ccic_notifier_notify(CC_NOTI_TYPEDEF *p_noti, void *pd, int pdic_attach)
{
int ret = 0;
ccic_notifier.ccic_template = *p_noti;
switch (p_noti->id) {
#ifdef CONFIG_USB_TYPEC_MANAGER_NOTIFIER
case CCIC_NOTIFY_ID_POWER_STATUS: // PDIC_NOTIFY_EVENT_PD_SINK
pr_info("%s: src:%01x dest:%01x id:%02x "
"attach:%02x cable_type:%02x rprd:%01x\n", __func__,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->src,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->dest,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->id,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->attach,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->cable_type,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->rprd);
if (pd != NULL) {
if (!((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->attach &&
((struct pdic_notifier_struct *)pd)->event != PDIC_NOTIFY_EVENT_CCIC_ATTACH) {
((struct pdic_notifier_struct *)pd)->event = PDIC_NOTIFY_EVENT_DETACH;
}
ccic_notifier.ccic_template.pd = pd;
pr_info("%s: PD event:%d, num:%d, sel:%d \n", __func__,
((struct pdic_notifier_struct *)pd)->event,
((struct pdic_notifier_struct *)pd)->sink_status.available_pdo_num,
((struct pdic_notifier_struct *)pd)->sink_status.selected_pdo_num);
}
break;
#endif
case CCIC_NOTIFY_ID_ATTACH:
pr_info("%s: src:%01x dest:%01x id:%02x "
"attach:%02x cable_type:%02x rprd:%01x\n", __func__,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->src,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->dest,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->id,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->attach,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->cable_type,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->rprd);
break;
case CCIC_NOTIFY_ID_RID:
pr_info("%s: src:%01x dest:%01x id:%02x rid:%02x\n", __func__,
((CC_NOTI_RID_TYPEDEF *)p_noti)->src,
((CC_NOTI_RID_TYPEDEF *)p_noti)->dest,
((CC_NOTI_RID_TYPEDEF *)p_noti)->id,
((CC_NOTI_RID_TYPEDEF *)p_noti)->rid);
#if defined(CONFIG_SEC_FACTORY)
ccic_uevent_work(CCIC_NOTIFY_ID_RID,((CC_NOTI_RID_TYPEDEF *)p_noti)->rid);
#endif
break;
#ifdef CONFIG_SEC_FACTORY
case CCIC_NOTIFY_ID_FAC:
pr_info("%s: src:%01x dest:%01x id:%02x ErrState:%02x\n", __func__,
p_noti->src, p_noti->dest, p_noti->id, p_noti->sub1);
ccic_uevent_work(CCIC_NOTIFY_ID_FAC, p_noti->sub1);
return 0;
#endif
case CCIC_NOTIFY_ID_WATER:
pr_info("%s: src:%01x dest:%01x id:%02x attach:%02x\n", __func__,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->src,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->dest,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->id,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->attach);
ccic_uevent_work(CCIC_NOTIFY_ID_WATER, ((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->attach);
#ifdef CONFIG_SEC_FACTORY
return 0;
#endif
break;
case CCIC_NOTIFY_ID_VCONN:
ccic_uevent_work(CCIC_NOTIFY_ID_VCONN, 0);
break;
case CCIC_NOTIFY_ID_ROLE_SWAP:
pr_info("%s: src:%01x dest:%01x id:%02x sub1:%02x\n", __func__,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->src,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->dest,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->id,
((CC_NOTI_ATTACH_TYPEDEF *)p_noti)->attach);
break;
default:
pr_info("%s: src:%01x dest:%01x id:%02x "
"sub1:%d sub2:%02x sub3:%02x\n", __func__,
((CC_NOTI_TYPEDEF *)p_noti)->src,
((CC_NOTI_TYPEDEF *)p_noti)->dest,
((CC_NOTI_TYPEDEF *)p_noti)->id,
((CC_NOTI_TYPEDEF *)p_noti)->sub1,
((CC_NOTI_TYPEDEF *)p_noti)->sub2,
((CC_NOTI_TYPEDEF *)p_noti)->sub3);
break;
}
#ifdef CONFIG_USB_NOTIFY_PROC_LOG
store_usblog_notify(NOTIFY_CCIC_EVENT, (void *)p_noti, NULL);
#endif
ret = blocking_notifier_call_chain(&(ccic_notifier.notifier_call_chain),
p_noti->id, &(ccic_notifier.ccic_template));
switch (ret) {
case NOTIFY_STOP_MASK:
case NOTIFY_BAD:
pr_err("%s: notify error occur(0x%x)\n", __func__, ret);
break;
case NOTIFY_DONE:
case NOTIFY_OK:
pr_info("%s: notify done(0x%x)\n", __func__, ret);
break;
default:
pr_info("%s: notify status unknown(0x%x)\n", __func__, ret);
break;
}
return ret;
}
int ccic_notifier_init(void)
{
int ret = 0;
pr_info("%s\n", __func__);
if(ccic_notifier_init_done)
{
pr_err("%s already registered\n", __func__);
goto out;
}
ccic_notifier_init_done = 1;
ccic_device = sec_device_create(0, NULL, "ccic");
if (IS_ERR(ccic_device)) {
pr_err("%s Failed to create device(switch)!\n", __func__);
ret = -ENODEV;
goto out;
}
/* create sysfs group */
ret = sysfs_create_group(&ccic_device->kobj, &ccic_sysfs_group);
if (ret) {
pr_err("%s: ccic sysfs fail, ret %d", __func__, ret);
goto out;
}
BLOCKING_INIT_NOTIFIER_HEAD(&(ccic_notifier.notifier_call_chain));
out:
return ret;
}
static void __exit ccic_notifier_exit(void)
{
pr_info("%s: exit\n", __func__);
}
device_initcall(ccic_notifier_init);
module_exit(ccic_notifier_exit);

662
drivers/ccic/ccic_sysfs.c Normal file
View File

@@ -0,0 +1,662 @@
/*
* ccic_sysfs.c
*
* Copyright (C) 2016 Samsung Electronics
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/types.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/ccic/ccic_sysfs.h>
#include <linux/ccic/s2mm005.h>
#include <linux/ccic/s2mm005_ext.h>
#include <linux/ccic/s2mm005_fw.h>
#include <linux/ccic/ccic_alternate.h>
static ssize_t s2mm005_cur_ver_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct s2mm005_version chip_swver;
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
if (!usbpd_data) {
pr_err("usbpd_data is NULL\n");
return -ENODEV;
}
s2mm005_get_chip_swversion(usbpd_data, &chip_swver);
pr_err("%s CHIP SWversion %2x %2x %2x %2x\n", __func__,
chip_swver.main[2] , chip_swver.main[1], chip_swver.main[0], chip_swver.boot);
usbpd_data->firm_ver[0] = chip_swver.main[2];
usbpd_data->firm_ver[1] = chip_swver.main[1];
usbpd_data->firm_ver[2] = chip_swver.main[0];
usbpd_data->firm_ver[3] = chip_swver.boot;
return sprintf(buf, "%02X %02X %02X %02X\n",
usbpd_data->firm_ver[0], usbpd_data->firm_ver[1],
usbpd_data->firm_ver[2], usbpd_data->firm_ver[3]);
}
static DEVICE_ATTR(cur_version, 0664, s2mm005_cur_ver_show, NULL);
static ssize_t s2mm005_src_ver_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
struct s2mm005_version fw_swver;
if (!usbpd_data) {
pr_err("usbpd_data is NULL\n");
return -ENODEV;
}
s2mm005_get_fw_version(usbpd_data->s2mm005_fw_product_id,
&fw_swver, usbpd_data->firm_ver[3], usbpd_data->hw_rev);
return sprintf(buf, "%02X %02X %02X %02X\n",
fw_swver.main[2], fw_swver.main[1], fw_swver.main[0], fw_swver.boot);
}
static DEVICE_ATTR(src_version, 0664, s2mm005_src_ver_show, NULL);
static ssize_t s2mm005_show_manual_lpm_mode(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
if (!usbpd_data) {
pr_err("usbpd_data is NULL\n");
return -ENODEV;
}
return sprintf(buf, "%d\n", usbpd_data->manual_lpm_mode);
}
static ssize_t s2mm005_store_manual_lpm_mode(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
int mode;
if (!usbpd_data) {
pr_err("usbpd_data is NULL\n");
return -ENODEV;
}
sscanf(buf, "%d", &mode);
pr_info("usb: %s mode=%d\n", __func__, mode);
switch(mode){
case 0:
/* Disable Low Power Mode for App (SW JIGON Disable) */
s2mm005_manual_JIGON(usbpd_data, 0);
usbpd_data->manual_lpm_mode = 0;
break;
case 1:
/* Enable Low Power Mode for App (SW JIGON Enable) */
s2mm005_manual_JIGON(usbpd_data, 1);
usbpd_data->manual_lpm_mode = 1;
break;
case 2:
/* SW JIGON Enable */
s2mm005_manual_JIGON(usbpd_data, 1);
// s2mm005_manual_LPM(usbpd_data, 0x1);
usbpd_data->manual_lpm_mode = 1;
break;
default:
/* SW JIGON Disable */
s2mm005_manual_JIGON(usbpd_data, 0);
usbpd_data->manual_lpm_mode = 0;
break;
}
return size;
}
static DEVICE_ATTR(lpm_mode, 0664,
s2mm005_show_manual_lpm_mode, s2mm005_store_manual_lpm_mode);
static ssize_t ccic_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
if(!usbpd_data) {
pr_err("%s usbpd_data is null!!\n", __func__);
return -ENODEV;
}
return sprintf(buf, "%d\n", usbpd_data->pd_state);
}
static DEVICE_ATTR(state, 0664, ccic_state_show, NULL);
#if defined(CONFIG_SEC_FACTORY)
static ssize_t ccic_rid_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
if(!usbpd_data) {
pr_err("%s usbpd_data is null!!\n", __func__);
return -ENODEV;
}
return sprintf(buf, "%d\n", usbpd_data->cur_rid);
}
static DEVICE_ATTR(rid, 0664, ccic_rid_show, NULL);
static ssize_t s2mm005_store_control_option_command(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
int cmd;
if(!usbpd_data) {
pr_err("%s usbpd_data is null!!\n", __func__);
return -ENODEV;
}
sscanf(buf, "%d", &cmd);
pr_info("usb: %s mode=%d\n", __func__, cmd);
s2mm005_control_option_command(usbpd_data, cmd);
return size;
}
static DEVICE_ATTR(ccic_control_option, 0664, NULL, s2mm005_store_control_option_command);
static ssize_t ccic_booting_dry_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
if(!usbpd_data) {
pr_err("%s usbpd_data is null!!\n", __func__);
return -ENODEV;
}
pr_info("%s booting_run_dry=%d\n", __func__,
usbpd_data->fac_booting_dry_check);
return sprintf(buf, "%d\n", (usbpd_data->fac_booting_dry_check));
}
static DEVICE_ATTR(booting_dry, 0444, ccic_booting_dry_show, NULL);
#endif
static int ccic_firmware_update_built_in(struct device *dev)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
struct s2mm005_version chip_swver, fw_swver, hwver;
s2mm005_get_chip_hwversion(usbpd_data, &hwver);
pr_err("%s CHIP HWversion %2x %2x %2x %2x\n", __func__,
hwver.main[2] , hwver.main[1], hwver.main[0], hwver.boot);
s2mm005_get_chip_swversion(usbpd_data, &chip_swver);
pr_err("%s CHIP SWversion %2x %2x %2x %2x - before\n", __func__,
chip_swver.main[2] , chip_swver.main[1], chip_swver.main[0], chip_swver.boot);
s2mm005_get_fw_version(usbpd_data->s2mm005_fw_product_id,
&fw_swver, chip_swver.boot, usbpd_data->hw_rev);
pr_err("%s SRC SWversion:%2x,%2x,%2x,%2x\n",__func__,
fw_swver.main[2], fw_swver.main[1], fw_swver.main[0], fw_swver.boot);
pr_err("%s: FW UPDATE boot:%01d hw_rev:%02d\n", __func__, chip_swver.boot, usbpd_data->hw_rev);
if(chip_swver.main[0] == fw_swver.main[0]) {
pr_err("%s: FW version is same. Stop FW update. src:%2x chip:%2x\n",
__func__, chip_swver.main[0], fw_swver.main[0]);
} else
s2mm005_flash_fw(usbpd_data, chip_swver.boot);
return 0;
}
static int ccic_firmware_update_ums(struct device *dev)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
unsigned char *fw_data;
struct s2mm005_fw *fw_hd;
struct file *fp;
mm_segment_t old_fs;
long fw_size, nread;
int error = 0;
if(!usbpd_data) {
pr_err("%s usbpd_data is null!!\n", __func__);
return -ENODEV;
}
old_fs = get_fs();
set_fs(KERNEL_DS);
fp = filp_open(CCIC_DEFAULT_UMS_FW, O_RDONLY, S_IRUSR);
if (IS_ERR(fp)) {
pr_err("%s: failed to open %s.\n", __func__,
CCIC_DEFAULT_UMS_FW);
error = -ENOENT;
goto open_err;
}
fw_size = fp->f_path.dentry->d_inode->i_size;
if (0 < fw_size) {
fw_data = kzalloc(fw_size, GFP_KERNEL);
nread = vfs_read(fp, (char __user *)fw_data, fw_size, &fp->f_pos);
pr_info("%s: start, file path %s, size %ld Bytes\n",
__func__, CCIC_DEFAULT_UMS_FW, fw_size);
filp_close(fp, NULL);
if (nread != fw_size) {
pr_err("%s: failed to read firmware file, nread %ld Bytes\n",
__func__, nread);
error = -EIO;
} else {
fw_hd = (struct s2mm005_fw*)fw_data;
pr_info("CCIC FW ver - cur:%02X %02X %02X %02X / bin:%02X %02X %02X %02X\n",
usbpd_data->firm_ver[0], usbpd_data->firm_ver[1], usbpd_data->firm_ver[2], usbpd_data->firm_ver[3],
fw_hd->boot, fw_hd->main[0], fw_hd->main[1], fw_hd->main[2]);
if(fw_hd->boot == usbpd_data->firm_ver[3]) {
if (s2mm005_flash_fw(usbpd_data, FLASH_WRITE_UMS) >= 0)
goto done;
} else {
pr_err("error : Didn't match to CCIC FW firmware version\n");
error = -EINVAL;
}
}
if (error < 0)
pr_err("%s: failed update firmware\n", __func__);
done:
kfree(fw_data);
}
open_err:
set_fs(old_fs);
return error;
}
static ssize_t ccic_store_firmware_status_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
u8 val = 0;
if(!usbpd_data) {
pr_err("%s usbpd_data is null!!\n", __func__);
return -ENODEV;
}
s2mm005_read_byte_flash(usbpd_data->i2c, FLASH_STATUS_0x24, &val, 1);
pr_err("%s flash mode: %s\n", __func__, flashmode_to_string(val));
return sprintf(buf, "%s\n", flashmode_to_string(val));
}
static DEVICE_ATTR(fw_update_status, 0444, ccic_store_firmware_status_show, NULL);
static ssize_t ccic_store_firmware_update(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
struct s2mm005_version version;
int mode = 0, ret = 1;
if (!usbpd_data) {
pr_err("usbpd_data is NULL\n");
return -ENODEV;
}
sscanf(buf, "%d", &mode);
pr_info("%s mode=%d\n", __func__, mode);
s2mm005_get_chip_swversion(usbpd_data, &version);
pr_err("%s CHIP SWversion %2x %2x %2x %2x - before\n", __func__,
version.main[2] , version.main[1], version.main[0], version.boot);
/* Factory cmd for firmware update
* argument represent what is source of firmware like below.
*
* 0 : [BUILT_IN] Getting firmware from source.
* 1 : [UMS] Getting firmware from sd card.
*/
switch (mode) {
case BUILT_IN:
ret = ccic_firmware_update_built_in(dev);
break;
case UMS:
ret = ccic_firmware_update_ums(dev);
break;
default:
pr_err("%s: Not support command[%d]\n",
__func__, mode);
break;
}
s2mm005_get_chip_swversion(usbpd_data, &version);
pr_err("%s CHIP SWversion %2x %2x %2x %2x - after\n", __func__,
version.main[2] , version.main[1], version.main[0], version.boot);
return size;
}
static DEVICE_ATTR(fw_update, 0220, NULL, ccic_store_firmware_update);
static ssize_t ccic_set_gpio(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
int mode;
u8 W_DATA[2];
u8 REG_ADD;
u8 R_DATA;
int i;
if (!usbpd_data) {
pr_err("usbpd_data is NULL\n");
return -ENODEV;
}
sscanf(buf, "%d", &mode);
pr_info("usb: %s mode=%d\n", __func__, mode);
/* for Wake up*/
for(i=0; i<5; i++){
R_DATA = 0x00;
REG_ADD = 0x8;
s2mm005_read_byte(usbpd_data->i2c, REG_ADD, &R_DATA, 1); //dummy read
}
udelay(10);
switch(mode){
case 0:
/* SBU1/SBU2 set as open-drain status*/
// SBU1/2 Open command ON
REG_ADD = 0x10;
W_DATA[0] = 0x03;
W_DATA[1] = 0x85;
s2mm005_write_byte(usbpd_data->i2c, REG_ADD, &W_DATA[0], 2);
break;
case 1:
/* SBU1/SBU2 set as default status */
// SBU1/2 Open command OFF
REG_ADD = 0x10;
W_DATA[0] = 0x03;
W_DATA[1] = 0x86;
s2mm005_write_byte(usbpd_data->i2c, REG_ADD, &W_DATA[0], 2);
break;
default:
break;
}
return size;
}
static ssize_t ccic_get_gpio(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
u8 W_DATA[4];
u8 REG_ADD;
u8 R_DATA;
int i;
if (!usbpd_data) {
pr_err("usbpd_data is NULL\n");
return -ENODEV;
}
/* for Wake up*/
for(i=0; i<5; i++){
R_DATA = 0x00;
REG_ADD = 0x8;
s2mm005_read_byte(usbpd_data->i2c, REG_ADD, &R_DATA, 1); //dummy read
}
udelay(10);
W_DATA[0] =0x2;
W_DATA[1] =0x10;
W_DATA[2] =0x84;
W_DATA[3] =0x10;
s2mm005_write_byte(usbpd_data->i2c, 0x10, &W_DATA[0], 4);
s2mm005_read_byte(usbpd_data->i2c, 0x14, &R_DATA, 1);
pr_err("%s SBU1 status = %2x , SBU2 status = %2x \n", __func__,
(R_DATA & 0x10) >> 4,(R_DATA & 0x20) >> 5);
return sprintf(buf, "%d %d\n", (R_DATA & 0x10) >> 4,(R_DATA & 0x20) >> 5);
}
static DEVICE_ATTR(control_gpio, 0664, ccic_get_gpio, ccic_set_gpio);
static ssize_t ccic_water_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
if(!usbpd_data) {
pr_err("%s usbpd_data is null!!\n", __func__);
return -ENODEV;
}
pr_info("%s water=%d, run_dry=%d\n", __func__,
usbpd_data->water_det, usbpd_data->run_dry);
return sprintf(buf, "%d\n", (usbpd_data->water_det | !usbpd_data->run_dry));
}
static DEVICE_ATTR(water, 0444, ccic_water_show, NULL);
#if defined(CONFIG_CCIC_ALTERNATE_MODE)
static ssize_t ccic_send_samsung_uVDM_message(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
int ret = 0;
if (!usbpd_data) {
pr_err("usbpd_data is NULL\n");
return -ENODEV;
}
ret = send_samsung_unstructured_vdm_message(usbpd_data, buf, size);
if( ret < 0 )
return ret;
else
return size;
}
static DEVICE_ATTR(samsung_uvdm, 0220, NULL, ccic_send_samsung_uVDM_message);
static ssize_t ccic_send_uVDM_message(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
int cmd = 0;
if (!usbpd_data) {
pr_err("usbpd_data is NULL\n");
return -ENODEV;
}
sscanf(buf, "%d", &cmd);
pr_info("%s cmd=%d\n", __func__, cmd);
send_unstructured_vdm_message(usbpd_data, cmd);
return size;
}
static DEVICE_ATTR(uvdm, 0220, NULL, ccic_send_uVDM_message);
static ssize_t ccic_send_dna_audio_uVDM_message(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
int cmd = 0;
if (!usbpd_data) {
pr_err("usbpd_data is NULL\n");
return -ENODEV;
}
sscanf(buf, "%d", &cmd);
pr_info("%s cmd=%d\n", __func__, cmd);
send_dna_audio_unstructured_vdm_message(usbpd_data, cmd);
return size;
}
static DEVICE_ATTR(dna_audio_uvdm, 0220, NULL, ccic_send_dna_audio_uVDM_message);
static ssize_t ccic_send_dex_fan_uVDM_message(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
int cmd = 0;
if (!usbpd_data) {
pr_err("usbpd_data is NULL\n");
return -ENODEV;
}
sscanf(buf, "%d", &cmd);
pr_info("%s cmd=%d\n", __func__, cmd);
send_dex_fan_unstructured_vdm_message(usbpd_data, cmd);
return size;
}
static DEVICE_ATTR(dex_fan_uvdm, 0220, NULL, ccic_send_dex_fan_uVDM_message);
static ssize_t ccic_send_attention_message(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
int cmd = 0;
if (!usbpd_data) {
pr_err("usbpd_data is NULL\n");
return -ENODEV;
}
sscanf(buf, "%d", &cmd);
pr_info("%s cmd=%d\n", __func__, cmd);
send_attention_message(usbpd_data, cmd);
return size;
}
static DEVICE_ATTR(attention, 0220, NULL, ccic_send_attention_message);
static ssize_t ccic_send_role_swap_message(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
int cmd = 0;
if (!usbpd_data) {
pr_err("usbpd_data is NULL\n");
return -ENODEV;
}
sscanf(buf, "%d", &cmd);
pr_info("%s cmd=%d\n", __func__, cmd);
send_role_swap_message(usbpd_data, cmd);
return size;
}
static DEVICE_ATTR(role_swap, 0220, NULL, ccic_send_role_swap_message);
static ssize_t ccic_acc_device_version_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
if (!usbpd_data) {
pr_err("%s usbpd_data is null!!\n", __func__);
return -ENODEV;
}
pr_info("%s 0x%04x\n", __func__, usbpd_data->Device_Version);
return sprintf(buf, "%04x\n", usbpd_data->Device_Version);
}
static DEVICE_ATTR(acc_device_version, 0444, ccic_acc_device_version_show,NULL);
static ssize_t ccic_usbpd_ids_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
int retval = 0;
if (!usbpd_data) {
pr_err("%s usbpd_data is null!!\n", __func__);
return -ENODEV;
}
retval = sprintf(buf, "%04x:%04x\n",
le16_to_cpu(usbpd_data->Vendor_ID),
le16_to_cpu(usbpd_data->Product_ID));
pr_info("usb: %s : %s",
__func__, buf);
return retval;
}
static DEVICE_ATTR(usbpd_ids, 0444, ccic_usbpd_ids_show, NULL);
static ssize_t ccic_usbpd_type_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct s2mm005_data *usbpd_data = dev_get_drvdata(dev);
int retval = 0;
if (!usbpd_data) {
pr_err("%s usbpd_data is null!!\n", __func__);
return -ENODEV;
}
retval = sprintf(buf, "%d\n", usbpd_data->acc_type);
pr_info("usb: %s : %d",
__func__, usbpd_data->acc_type);
return retval;
}
static DEVICE_ATTR(usbpd_type, 0444, ccic_usbpd_type_show, NULL);
#endif
static struct attribute *ccic_attributes[] = {
&dev_attr_cur_version.attr,
&dev_attr_src_version.attr,
&dev_attr_lpm_mode.attr,
&dev_attr_state.attr,
#if defined(CONFIG_SEC_FACTORY)
&dev_attr_rid.attr,
&dev_attr_ccic_control_option.attr,
&dev_attr_booting_dry.attr,
#endif
&dev_attr_fw_update.attr,
&dev_attr_fw_update_status.attr,
&dev_attr_control_gpio.attr,
&dev_attr_water.attr,
#if defined(CONFIG_CCIC_ALTERNATE_MODE)
&dev_attr_uvdm.attr,
&dev_attr_dna_audio_uvdm.attr,
&dev_attr_dex_fan_uvdm.attr,
&dev_attr_samsung_uvdm.attr,
&dev_attr_attention.attr,
&dev_attr_role_swap.attr,
&dev_attr_acc_device_version.attr,
&dev_attr_usbpd_ids.attr,
&dev_attr_usbpd_type.attr,
#endif
NULL
};
const struct attribute_group ccic_sysfs_group = {
.attrs = ccic_attributes,
};

View File

@@ -0,0 +1,173 @@
#include <linux/device.h>
#include <linux/notifier.h>
#include <linux/ccic/pdic_notifier.h>
#define SET_PDIC_NOTIFIER_BLOCK(nb, fn, dev) do { \
(nb)->notifier_call = (fn); \
(nb)->priority = (dev); \
} while (0)
#define DESTROY_PDIC_NOTIFIER_BLOCK(nb) \
SET_PDIC_NOTIFIER_BLOCK(nb, NULL, -1)
static struct s2mu004_pdic_notifier_struct pdic_notifier;
int s2mu004_pdic_notifier_register(struct notifier_block *nb, notifier_fn_t notifier,
muic_notifier_device_t listener)
{
int ret = 0;
pr_info("%s: listener=%d register\n", __func__, listener);
SET_PDIC_NOTIFIER_BLOCK(nb, notifier, listener);
ret = blocking_notifier_chain_register(&(pdic_notifier.notifier_call_chain), nb);
if (ret < 0)
pr_err("%s: blocking_notifier_chain_register error(%d)\n",
__func__, ret);
/* current pdic's attached_device status notify */
nb->notifier_call(nb, pdic_notifier.cmd,
&(pdic_notifier.attached_dev));
return ret;
}
int s2mu004_pdic_notifier_unregister(struct notifier_block *nb)
{
int ret = 0;
pr_info("%s: listener=%d unregister\n", __func__, nb->priority);
ret = blocking_notifier_chain_unregister(&(pdic_notifier.notifier_call_chain), nb);
if (ret < 0)
pr_err("%s: blocking_notifier_chain_unregister error(%d)\n",
__func__, ret);
DESTROY_PDIC_NOTIFIER_BLOCK(nb);
return ret;
}
static int s2mu004_pdic_notifier_notify(void)
{
int ret = 0;
pr_info("%s: CMD=%d, DATA=%d\n", __func__, pdic_notifier.cmd,
pdic_notifier.attached_dev);
ret = blocking_notifier_call_chain(&(pdic_notifier.notifier_call_chain),
pdic_notifier.cmd, &(pdic_notifier.attached_dev));
switch (ret) {
case NOTIFY_STOP_MASK:
case NOTIFY_BAD:
pr_err("%s: notify error occur(0x%x)\n", __func__, ret);
break;
case NOTIFY_DONE:
case NOTIFY_OK:
pr_info("%s: notify done(0x%x)\n", __func__, ret);
break;
default:
pr_info("%s: notify status unknown(0x%x)\n", __func__, ret);
break;
}
return ret;
}
void s2mu004_pdic_notifier_attach_attached_jig_dev(muic_attached_dev_t new_dev)
{
pr_info("%s: (%d)\n", __func__, new_dev);
pdic_notifier.cmd = PDIC_MUIC_NOTIFY_CMD_JIG_ATTACH;
pdic_notifier.attached_dev = new_dev;
/* pdic's attached_device attach broadcast */
s2mu004_pdic_notifier_notify();
}
#if 0
void s2mu004_pdic_notifier_detach_attached_jig_dev(muic_attached_dev_t cur_dev)
{
pr_info("%s: (%d)\n", __func__, cur_dev);
pdic_notifier.cmd = PDIC_MUIC_NOTIFY_CMD_JIG_DETACH;
if (pdic_notifier.attached_dev != cur_dev)
pr_warn("%s: attached_dev of pdic_notifier(%d) != pdic_data(%d)\n",
__func__, pdic_notifier.attached_dev, cur_dev);
if (pdic_notifier.attached_dev != ATTACHED_DEV_NONE_MUIC) {
/* pdic's attached_device detach broadcast */
s2mu004_pdic_notifier_notify();
}
pdic_notifier.attached_dev = ATTACHED_DEV_NONE_MUIC;
}
#endif
void s2mu004_pdic_notifier_attach_attached_dev(muic_attached_dev_t new_dev)
{
pr_info("%s: (%d)\n", __func__, new_dev);
pdic_notifier.cmd = MUIC_NOTIFY_CMD_ATTACH;
pdic_notifier.attached_dev = new_dev;
/* pdic's attached_device attach broadcast */
s2mu004_pdic_notifier_notify();
}
void s2mu004_pdic_notifier_detach_attached_dev(muic_attached_dev_t cur_dev)
{
pr_info("%s: (%d)\n", __func__, cur_dev);
pdic_notifier.cmd = MUIC_NOTIFY_CMD_DETACH;
if (pdic_notifier.attached_dev != cur_dev)
pr_warn("%s: attached_dev of pdic_notifier(%d) != pdic_data(%d)\n",
__func__, pdic_notifier.attached_dev, cur_dev);
if (pdic_notifier.attached_dev != ATTACHED_DEV_NONE_MUIC) {
/* pdic's attached_device detach broadcast */
s2mu004_pdic_notifier_notify();
}
pdic_notifier.attached_dev = ATTACHED_DEV_NONE_MUIC;
}
void s2mu004_pdic_notifier_logically_attach_attached_dev(muic_attached_dev_t new_dev)
{
pr_info("%s: (%d)\n", __func__, new_dev);
pdic_notifier.cmd = MUIC_NOTIFY_CMD_LOGICALLY_ATTACH;
pdic_notifier.attached_dev = new_dev;
/* pdic's attached_device attach broadcast */
s2mu004_pdic_notifier_notify();
}
void s2mu004_pdic_notifier_logically_detach_attached_dev(muic_attached_dev_t cur_dev)
{
pr_info("%s: (%d)\n", __func__, cur_dev);
pdic_notifier.cmd = MUIC_NOTIFY_CMD_LOGICALLY_DETACH;
pdic_notifier.attached_dev = cur_dev;
/* pdic's attached_device detach broadcast */
s2mu004_pdic_notifier_notify();
pdic_notifier.attached_dev = ATTACHED_DEV_NONE_MUIC;
}
static int __init s2mu004_pdic_notifier_init(void)
{
int ret = 0;
pr_info("%s\n", __func__);
BLOCKING_INIT_NOTIFIER_HEAD(&(pdic_notifier.notifier_call_chain));
pdic_notifier.cmd = MUIC_NOTIFY_CMD_DETACH;
pdic_notifier.attached_dev = ATTACHED_DEV_UNKNOWN_MUIC;
return ret;
}
subsys_initcall(s2mu004_pdic_notifier_init);

1396
drivers/ccic/s2mm005.c Normal file

File diff suppressed because it is too large Load Diff

1339
drivers/ccic/s2mm005_cc.c Normal file

File diff suppressed because it is too large Load Diff

607
drivers/ccic/s2mm005_fw.c Normal file
View File

@@ -0,0 +1,607 @@
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/ccic/s2mm005.h>
#include <linux/ccic/s2mm005_ext.h>
#include <linux/ccic/s2mm005_fw.h>
#include <linux/ccic/ccic_sysfs.h>
#include <linux/ccic/BOOT_FLASH_FW_BOOT5.h>
#include <linux/ccic/BOOT_FLASH_FW_BOOT6.h>
#include <linux/ccic/BOOT_FLASH_FW_0x0B_BOOT7.h>
#include <linux/ccic/BOOT_FLASH_FW_0x0C_BOOT8.h>
#include <linux/ccic/BOOT_FLASH_FW_0x11_BOOT8.h>
#include <linux/ccic/BOOT_FLASH_FW_0x12_BOOT8.h>
#include <linux/ccic/BOOT_FLASH_FW_0x14_BOOT8.h>
#include <linux/ccic/BOOT_SRAM_FW.h>
#define S2MM005_FIRMWARE_PATH "usbpd/s2mm005.bin"
#define FW_CHECK_RETRY 5
#define VALID_FW_BOOT_VERSION(fw_boot) (fw_boot == 0x7)
#define VALID_FW_MAIN_VERSION(fw_main) \
(!((fw_main[0] == 0xff) && (fw_main[1] == 0xff)) \
&& !((fw_main[0] == 0x00) && (fw_main[1] == 0x00)))
const char *flashmode_to_string(u32 mode)
{
switch (mode) {
#define FLASH_MODE_STR(mode) case mode: return(#mode)
FLASH_MODE_STR(FLASH_MODE_NORMAL);
FLASH_MODE_STR(FLASH_MODE_FLASH);
FLASH_MODE_STR(FLASH_MODE_ERASE);
}
return "?";
}
int s2mm005_sram_write(const struct i2c_client *i2c)
{
int ret = 0;
struct i2c_msg msg[1];
struct s2mm005_data *usbpd_data = i2c_get_clientdata(i2c);
pr_err("%s size:%d\n", __func__, BOOT_SRAM_FW_SIZE);
mutex_lock(&usbpd_data->i2c_mutex);
msg[0].addr = 0x3B; /* Slave addr 0x76 */
msg[0].flags = 0;
msg[0].len = BOOT_SRAM_FW_SIZE;
msg[0].buf = (u8 *)&BOOT_SRAM_FW[0];
ret = i2c_transfer(i2c->adapter, msg, 1);
if (ret < 0)
dev_err(&i2c->dev, "i2c write fail error %d\n", ret);
mutex_unlock(&usbpd_data->i2c_mutex);
return ret;
}
void s2mm005_write_flash(const struct i2c_client *i2c,
unsigned int fAddr, unsigned int fData) {
u8 data[8];
data[0] = 0x42;
data[1] = 0x04; /* long write */
data[2] = (u8)(fAddr & 0xFF);
data[3] = (u8)((fAddr >> 8) & 0xFF);
data[4] = (uint8_t)(fData & 0xFF);
data[5] = (uint8_t)((fData>>8) & 0xFF);
data[6] = (uint8_t)((fData>>16) & 0xFF);
data[7] = (uint8_t)((fData>>24) & 0xFF);
/* pr_info("Flash Write Address :0x%08X Data:0x%08X\n", fAddr, fData);*/
s2mm005_write_byte(i2c, 0x10, &data[0], 8);
}
void s2mm005_verify_flash(const struct i2c_client *i2c,
uint32_t fAddr, uint32_t *fData) {
uint16_t REG_ADD;
uint8_t W_DATA[8];
uint8_t R_DATA[4];
uint32_t fRead[3];
uint32_t fCnt;
for (fCnt = 0; fCnt < 2; fCnt++) {
W_DATA[0] = 0x42;
W_DATA[1] = 0x40; /* Long Read */
W_DATA[2] = (uint8_t)(fAddr & 0xFF);
W_DATA[3] = (uint8_t)((fAddr>>8) & 0xFF);
REG_ADD = 0x10;
udelay(10);
s2mm005_write_byte(i2c, REG_ADD, &W_DATA[0], 4);
REG_ADD = 0x14;
udelay(10);
s2mm005_read_byte_flash(i2c, REG_ADD, &R_DATA[0], 4);
fRead[fCnt] = 0;
fRead[fCnt] |= R_DATA[0];
fRead[fCnt] |= (R_DATA[1]<<8);
fRead[fCnt] |= (R_DATA[2]<<16);
fRead[fCnt] |= (R_DATA[3]<<24);
}
if (fRead[0] == fRead[1]) {
*fData = fRead[0];
/* pr_err("> Flash Read Address : 0x%08X Data : 0x%08X\n",fAddr, *fData); */
} else {
W_DATA[0] = 0x42;
W_DATA[1] = 0x40; /* Long Read */
W_DATA[2] = (uint8_t)(fAddr & 0xFF);
W_DATA[3] = (uint8_t)((fAddr>>8) & 0xFF);
REG_ADD = 0x10;
udelay(10);
s2mm005_write_byte(i2c, REG_ADD, &W_DATA[0], 4);
REG_ADD = 0x14;
udelay(10);
s2mm005_read_byte_flash(i2c, REG_ADD, &R_DATA[0], 4);
fRead[fCnt] = 0;
fRead[2] |= R_DATA[0];
fRead[2] |= (R_DATA[1]<<8);
fRead[2] |= (R_DATA[2]<<16);
fRead[2] |= (R_DATA[3]<<24);
if (fRead[2] == fRead[0]) {
*fData = fRead[0];
pr_err("> Flash Read[0] Address : 0x%08X Data : 0x%08X\n", fAddr, *fData);
} else if (fRead[2] == fRead[1]) {
*fData = fRead[1];
pr_err("> Flash Read[1] Address : 0x%08X Data : 0x%08X\n", fAddr, *fData);
} else {
*fData = 0;
pr_err("> Flash Read[FAIL] Address : 0x%08X Data : 0x%08X\n", fAddr, *fData);
}
}
}
static int s2mm005_flash_write(struct s2mm005_data *usbpd_data, unsigned char *fw_data)
{
struct i2c_client *i2c = usbpd_data->i2c;
u8 val, reg, fError;
uint32_t *pFlash_FW, *pFlash_FWCS;
uint32_t LopCnt, fAddr, fData, fRData, sLopCnt;
uint32_t recheck_count = 0;
struct s2mm005_fw *fw_hd;
unsigned int size;
reg = FLASH_WRITE_0x42;
s2mm005_write_byte(i2c, CMD_MODE_0x10, &reg, 1);
reg = FLASH_WRITING_BYTE_SIZE_0x4;
s2mm005_write_byte(i2c, CMD_HOST_0x11, &reg, 1);
s2mm005_read_byte_flash(i2c, FLASH_STATUS_0x24, &val, 1);
pFlash_FW = (uint32_t *)(fw_data + 12);
pFlash_FWCS = (uint32_t *)fw_data;
fw_hd = (struct s2mm005_fw*)fw_data;
size = fw_hd -> size;
if(fw_hd -> boot < 6)
sLopCnt = 0x1000/4;
else if (fw_hd -> boot == 6)
sLopCnt = 0x8000/4;
else if (fw_hd -> boot == 7)
sLopCnt = 0x7000/4;
else if (fw_hd -> boot >= 8)
sLopCnt = 0x5000/4;
/* Flash write */
for (LopCnt = sLopCnt; LopCnt < (size/4); LopCnt++) {
fAddr = LopCnt*4;
fData = pFlash_FW[LopCnt];
udelay(10);
s2mm005_write_flash(i2c, fAddr, fData);
}
usleep_range(10 * 1000, 10 * 1000);
/* Partial verify */
while(1) {
for (LopCnt = sLopCnt; LopCnt < (size/4); LopCnt++) {
fError = 1;
fAddr = LopCnt*4;
fData = pFlash_FW[LopCnt];
s2mm005_verify_flash(i2c, fAddr, &fRData);
if (fData != fRData) {
recheck_count++;
LopCnt = (fAddr & 0xffffff00) / 4;
sLopCnt = LopCnt;
fError = 0;
pr_err("%s partial verify fail!! recheck count : %d\n", __func__, recheck_count);
pr_err("Verify Error Address = 0x%08X WData = 0x%08X VData = 0x%08X\n", fAddr, fData, fRData);
s2mm005_write_flash(i2c, fAddr, fData);
msleep(20);
if(recheck_count == 1000)
return -EFLASH_VERIFY;
break;
}
}
if(fError)
break;
}
pr_err("%s verify success!! recheck count : %d\n", __func__, recheck_count);
if (LopCnt >= (size / 4)) {
if (fw_hd -> boot >= 6) {
/* SW version from header */
recheck_count = 0;
while(1) {
recheck_count++;
fAddr = 0xEFF0;
pr_err("SW version = 0x%08X\n", pFlash_FWCS[0]);
fData = pFlash_FWCS[0];
s2mm005_write_flash(i2c, fAddr, fData);
fRData = 0;
s2mm005_verify_flash(i2c, fAddr, &fRData);
if(fData == fRData)
break;
else {
if (recheck_count == 30)
return -EFLASH_WRITE_SWVERSION;
}
}
/* Size from header */
recheck_count = 0;
while(1) {
recheck_count++;
fAddr = 0xEFF4;
pr_err("SW Size = 0x%08X\n", pFlash_FWCS[2]);
fData = pFlash_FWCS[2];
s2mm005_write_flash(i2c, fAddr, fData);
fRData = 0;
s2mm005_verify_flash(i2c, fAddr, &fRData);
if(fData == fRData)
break;
else {
if (recheck_count == 30)
return -EFLASH_WRITE_SIZE;
}
}
/* CRC Check sum */
recheck_count = 0;
while(1) {
recheck_count++;
fAddr = 0xEFF8;
pr_err("SW CheckSum = 0x%08X\n", pFlash_FWCS[((size + 16) / 4) - 1]);
fData = pFlash_FWCS[((size + 16) / 4) - 1];
s2mm005_write_flash(i2c, fAddr, fData);
fRData = 0;
s2mm005_verify_flash(i2c, fAddr, &fRData);
if(fData == fRData)
break;
else {
if (recheck_count == 30)
return -EFLASH_WRITE_CRC;
}
}
}
/* flash done */
recheck_count = 0;
while(1)
{
recheck_count++;
fAddr = 0xEFFC;
fData = 0x1;
s2mm005_write_flash(i2c, fAddr, fData);
fRData = 0;
s2mm005_verify_flash(i2c, fAddr, &fRData);
pr_err("0xeffc = %d\n", fRData);
if(fData == fRData)
break;
else {
if (recheck_count == 30)
return -EFLASH_WRITE_DONE;
}
}
pr_err("%s flash write succesfully done!!\n", __func__);
}
return 0;
}
void s2mm005_flash_ready(struct s2mm005_data *usbpd_data)
{
struct i2c_client *i2c = usbpd_data->i2c;
u8 W_DATA[5];
/* FLASH_READY */
W_DATA[0] = 0x02;
W_DATA[1] = 0x01;
W_DATA[2] = 0x30;
W_DATA[3] = 0x50;
W_DATA[4] = 0x01;
s2mm005_write_byte(i2c, CMD_MODE_0x10, &W_DATA[0], 5);
}
int s2mm005_flash(struct s2mm005_data *usbpd_data, unsigned int input)
{
struct i2c_client *i2c = usbpd_data->i2c;
u8 val, reg;
int ret = 0;
int retry = 0;
struct s2mm005_fw *fw_hd;
struct file *fp;
mm_segment_t old_fs;
long fw_size, nread;
int irq_gpio_status;
FLASH_STATE_Type Flash_DATA;
switch (input) {
case FLASH_MODE_ENTER: { /* enter flash mode */
/* FLASH_READY */
s2mm005_flash_ready(usbpd_data);
do {
/* FLASH_MODE */
reg = FLASH_MODE_ENTER_0x10;
s2mm005_write_byte(i2c, CMD_MODE_0x10, &reg, 1);
usleep_range(50 * 1000, 50 * 1000);
/* If irq status is not clear, CCIC can not enter flash mode. */
irq_gpio_status = gpio_get_value(usbpd_data->irq_gpio);
dev_info(&i2c->dev, "%s IRQ0:%02d\n", __func__, irq_gpio_status);
if(!irq_gpio_status) {
s2mm005_int_clear(usbpd_data); // interrupt clear
usleep_range(10 * 1000, 10 * 1000);
}
s2mm005_read_byte_flash(i2c, FLASH_STATUS_0x24, &val, 1);
pr_err("%s %s retry %d\n", __func__, flashmode_to_string(val), retry);
usleep_range(50*1000, 50*1000);
s2mm005_read_byte(i2c, 0x24, Flash_DATA.BYTE, 4);
dev_info(&i2c->dev, "Flash_State:0x%02X Reserved:0x%06X\n",
Flash_DATA.BITS.Flash_State, Flash_DATA.BITS.Reserved);
retry++;
if(retry == 10) {
/* RESET */
s2mm005_reset(usbpd_data);
msleep(3000);
s2mm005_flash_ready(usbpd_data);
} else if (retry == 20) {
panic("Flash mode change fail!\n");
}
} while (val != FLASH_MODE_FLASH);
break;
}
case FLASH_ERASE: { /* erase flash */
reg = FLASH_ERASE_0x44;
usleep_range(10 * 1000, 10 * 1000);
s2mm005_write_byte(i2c, CMD_MODE_0x10, &reg, 1);
msleep(200);
s2mm005_read_byte_flash(i2c, FLASH_STATUS_0x24, &val, 1);
pr_err("flash mode : %s\n", flashmode_to_string(val));
break;
}
case FLASH_WRITE7: { /* write flash & verify */
switch (usbpd_data->s2mm005_fw_product_id) {
case PRODUCT_NUM_ASTAR_DREAMLITE:
default:
ret = s2mm005_flash_write(usbpd_data, (unsigned char*)&BOOT_FLASH_FW_0x0B_BOOT7[0]);
break;
}
break;
}
case FLASH_WRITE8: { /* write flash & verify */
switch (usbpd_data->s2mm005_fw_product_id) {
case PRODUCT_NUM_KELLY:
ret = s2mm005_flash_write(usbpd_data, (unsigned char*)&BOOT_FLASH_FW_0x0C_BOOT8[0]);
break;
case PRODUCT_NUM_A8S:
ret = s2mm005_flash_write(usbpd_data, (unsigned char*)&BOOT_FLASH_FW_0x11_BOOT8[0]);
break;
case PRODUCT_NUM_TABS4LV:
ret = s2mm005_flash_write(usbpd_data, (unsigned char*)&BOOT_FLASH_FW_0x12_BOOT8[0]);
break;
case PRODUCT_NUM_GTACTIVEXL:
ret = s2mm005_flash_write(usbpd_data, (unsigned char*)&BOOT_FLASH_FW_0x14_BOOT8[0]);
break;
default:
ret = s2mm005_flash_write(usbpd_data, (unsigned char*)&BOOT_FLASH_FW_0x12_BOOT8[0]);
break;
}
break;
}
case FLASH_WRITE_UMS: {
old_fs = get_fs();
set_fs(KERNEL_DS);
fp = filp_open(CCIC_DEFAULT_UMS_FW, O_RDONLY, S_IRUSR);
if (IS_ERR(fp)) {
pr_err("%s: failed to open %s.\n", __func__,
CCIC_DEFAULT_UMS_FW);
ret = -ENOENT;
set_fs(old_fs);
return ret;
}
fw_size = fp->f_path.dentry->d_inode->i_size;
if (0 < fw_size) {
unsigned char *fw_data;
fw_data = kzalloc(fw_size, GFP_KERNEL);
nread = vfs_read(fp, (char __user *)fw_data,
fw_size, &fp->f_pos);
pr_err("%s: start, file path %s, size %ld Bytes\n",
__func__, CCIC_DEFAULT_UMS_FW, fw_size);
if (nread != fw_size) {
pr_err("%s: failed to read firmware file, nread %ld Bytes\n",
__func__, nread);
ret = -EIO;
} else {
fw_hd = (struct s2mm005_fw*)fw_data;
pr_err("%02X %02X %02X %02X size:%05d\n", fw_hd->boot, fw_hd->main[0], fw_hd->main[1], fw_hd->main[2], fw_hd->size);
/* TODO : DISABLE IRQ */
/* TODO : FW UPDATE */
ret = s2mm005_flash_write(usbpd_data, (unsigned char*)fw_data);
/* TODO : ENABLE IRQ */
}
kfree(fw_data);
}
filp_close(fp, NULL);
set_fs(old_fs);
break;
}
case FLASH_MODE_EXIT: { /* exit flash mode */
reg = FLASH_MODE_EXIT_0x20;
s2mm005_write_byte(i2c, CMD_MODE_0x10, &reg, 1);
usleep_range(15 * 1000, 15 * 1000);
s2mm005_read_byte_flash(i2c, FLASH_STATUS_0x24, &val, 1);
pr_err("flash mode : %s\n", flashmode_to_string(val));
break;
}
default: {
pr_err("Flash value does not matched menu\n");
}
}
return ret;
}
void s2mm005_get_fw_version(int product_id,
struct s2mm005_version *version, u8 boot_version, u32 hw_rev)
{
struct s2mm005_fw *fw_hd = NULL;
switch (boot_version) {
case 5:
fw_hd = (struct s2mm005_fw*) BOOT_FLASH_FW_BOOT5;
break;
case 6:
fw_hd = (struct s2mm005_fw*) BOOT_FLASH_FW_BOOT6;
break;
case 7:
switch (product_id) {
case PRODUCT_NUM_ASTAR_DREAMLITE:
default:
fw_hd = (struct s2mm005_fw*) BOOT_FLASH_FW_0x0B_BOOT7;
break;
}
case 8:
switch (product_id) {
case PRODUCT_NUM_KELLY:
fw_hd = (struct s2mm005_fw*) BOOT_FLASH_FW_0x0C_BOOT8;
break;
case PRODUCT_NUM_A8S:
fw_hd = (struct s2mm005_fw*) BOOT_FLASH_FW_0x11_BOOT8;
break;
case PRODUCT_NUM_TABS4LV:
fw_hd = (struct s2mm005_fw*) BOOT_FLASH_FW_0x12_BOOT8;
break;
case PRODUCT_NUM_GTACTIVEXL:
fw_hd = (struct s2mm005_fw*) BOOT_FLASH_FW_0x14_BOOT8;
break;
default:
fw_hd = (struct s2mm005_fw*) BOOT_FLASH_FW_0x12_BOOT8;
break;
}
break;
}
if(fw_hd != NULL)
{
version->boot = fw_hd->boot;
version->main[0] = fw_hd->main[0];
version->main[1] = fw_hd->main[1];
version->main[2] = fw_hd->main[2];
} else {
version->boot = 0;
version->main[0] = 0;
version->main[1] = 0;
version->main[2] = 0;
}
}
void s2mm005_get_chip_hwversion(struct s2mm005_data *usbpd_data,
struct s2mm005_version *version)
{
struct i2c_client *i2c = usbpd_data->i2c;
s2mm005_read_byte_flash(i2c, 0x0, (u8 *)&version->boot, 1);
s2mm005_read_byte_flash(i2c, 0x1, (u8 *)&version->main, 3);
s2mm005_read_byte_flash(i2c, 0x4, (u8 *)&version->ver2, 4);
}
void s2mm005_get_chip_swversion(struct s2mm005_data *usbpd_data,
struct s2mm005_version *version)
{
struct i2c_client *i2c = usbpd_data->i2c;
int i;
for(i=0; i < FW_CHECK_RETRY; i++) {
s2mm005_read_byte_flash(i2c, 0x8, (u8 *)&version->boot, 1);
if(VALID_FW_BOOT_VERSION(version->boot))
break;
}
for(i=0; i < FW_CHECK_RETRY; i++) {
s2mm005_read_byte_flash(i2c, 0x9, (u8 *)&version->main, 3);
if(VALID_FW_MAIN_VERSION(version->main))
break;
}
for (i = 0; i < FW_CHECK_RETRY; i++)
s2mm005_read_byte_flash(i2c, 0xc, (u8 *)&version->ver2, 4);
}
int s2mm005_check_version(struct s2mm005_version *version1,
struct s2mm005_version *version2)
{
if (version1->boot != version2->boot) {
return FLASH_FW_VER_BOOT;
}
if (memcmp(version1->main, version2->main, 3)) {
return FLASH_FW_VER_MAIN;
}
return FLASH_FW_VER_MATCH;
}
int s2mm005_flash_fw(struct s2mm005_data *usbpd_data, unsigned int input)
{
int ret = 0;
u8 val = 0;
if( usbpd_data->fw_product_id != usbpd_data->s2mm005_fw_product_id)
{
pr_err("FW_UPDATE fail, product number is different (%d)(%d) \n", usbpd_data->fw_product_id,usbpd_data->s2mm005_fw_product_id);
return 0;
}
pr_err("FW_UPDATE %d\n", input);
switch (input) {
case FLASH_WRITE5:
case FLASH_WRITE6:
case FLASH_WRITE7:
case FLASH_WRITE8:
case FLASH_WRITE: {
s2mm005_flash(usbpd_data, FLASH_MODE_ENTER);
usleep_range(10 * 1000, 10 * 1000);
s2mm005_flash(usbpd_data, FLASH_ERASE);
msleep(200);
ret = s2mm005_flash(usbpd_data, input);
if (ret < 0)
return ret;
usleep_range(10 * 1000, 10 * 1000);
s2mm005_flash(usbpd_data, FLASH_MODE_EXIT);
usleep_range(10 * 1000, 10 * 1000);
s2mm005_reset(usbpd_data);
usleep_range(10 * 1000, 10 * 1000);
break;
}
case FLASH_WRITE_UMS: {
s2mm005_read_byte_flash(usbpd_data->i2c, FLASH_STATUS_0x24, &val, 1);
if(val != FLASH_MODE_NORMAL) {
pr_err("Can't CCIC FW update: cause by %s\n", flashmode_to_string(val));
}
disable_irq(usbpd_data->irq);
s2mm005_manual_LPM(usbpd_data, 0x7); // LP Off
msleep(3000);
s2mm005_flash(usbpd_data, FLASH_MODE_ENTER);
usleep_range(10 * 1000, 10 * 1000);
s2mm005_flash(usbpd_data, FLASH_ERASE);
msleep(200);
ret = s2mm005_flash(usbpd_data, input);
if (ret < 0)
panic("infinite write fail!\n");
usleep_range(10 * 1000, 10 * 1000);
s2mm005_flash(usbpd_data, FLASH_MODE_EXIT);
usleep_range(10 * 1000, 10 * 1000);
s2mm005_reset(usbpd_data);
usleep_range(10 * 1000, 10 * 1000);
s2mm005_manual_LPM(usbpd_data, 0x6); // LP On
enable_irq(usbpd_data->irq);
break;
}
default: {
break;
}
}
return 0;
}

444
drivers/ccic/s2mm005_pd.c Normal file
View File

@@ -0,0 +1,444 @@
/*
* driver/../s2mm005.c - S2MM005 USB PD function driver
*
* Copyright (C) 2015 Samsung Electronics
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/ccic/s2mm005_ext.h>
#include <linux/power_supply.h>
#if defined(CONFIG_BATTERY_NOTIFIER)
#include <linux/battery/battery_notifier.h>
#endif
#include <linux/usb_notify.h>
#if defined(CONFIG_CCIC_ALTERNATE_MODE)
#include <linux/ccic/ccic_alternate.h>
#endif
#ifdef CONFIG_MUIC_SM5705_SWITCH_CONTROL_GPIO
extern int muic_GPIO_control(int gpio);
#endif
struct pdic_notifier_struct pd_noti;
////////////////////////////////////////////////////////////////////////////////
// function definition
////////////////////////////////////////////////////////////////////////////////
void select_pdo(int num);
void s2mm005_select_pdo(int num);
void select_pdo(int num);
void (*fp_select_pdo)(int num);
void vbus_turn_on_ctrl(bool enable);
void process_pd(void *data, u8 plug_attach_done, u8 *pdic_attach, MSG_IRQ_STATUS_Type *MSG_IRQ_State);
////////////////////////////////////////////////////////////////////////////////
// PD function will be merged
////////////////////////////////////////////////////////////////////////////////
static inline struct power_supply *get_power_supply_by_name(char *name)
{
if (!name)
return (struct power_supply *)NULL;
else
return power_supply_get_by_name(name);
}
void s2mm005_select_pdo(int num)
{
struct s2mm005_data *usbpd_data = pd_noti.pusbpd;
uint8_t CMD_DATA[3];
if (pd_noti.sink_status.selected_pdo_num == num) {
pr_info("%s num(%d)\n", __func__, num);
ccic_event_work(usbpd_data, CCIC_NOTIFY_DEV_BATTERY,
CCIC_NOTIFY_ID_POWER_STATUS, 1/*attach*/, 0, 0);
return;
} else if (num > pd_noti.sink_status.available_pdo_num)
pd_noti.sink_status.selected_pdo_num = pd_noti.sink_status.available_pdo_num;
else if (num < 1)
pd_noti.sink_status.selected_pdo_num = 1;
else
pd_noti.sink_status.selected_pdo_num = num;
pr_info(" %s : PDO(%d) is selected to change\n", __func__, pd_noti.sink_status.selected_pdo_num);
CMD_DATA[0] = 0x3;
CMD_DATA[1] = 0x3;
CMD_DATA[2] = pd_noti.sink_status.selected_pdo_num;
s2mm005_write_byte(pd_noti.pusbpd->i2c, REG_I2C_SLV_CMD, &CMD_DATA[0], 3);
CMD_DATA[0] = 0x3;
CMD_DATA[1] = 0x2;
CMD_DATA[2] = State_PE_SNK_Wait_for_Capabilities;
s2mm005_write_byte(pd_noti.pusbpd->i2c, REG_I2C_SLV_CMD, &CMD_DATA[0], 3);
}
void select_pdo(int num)
{
if (fp_select_pdo)
fp_select_pdo(num);
}
void vbus_turn_on_ctrl(bool enable)
{
struct power_supply *psy_otg;
union power_supply_propval val;
int on = !!enable;
int ret = 0;
#if defined(CONFIG_USB_HOST_NOTIFY)
struct otg_notify *o_notify = get_otg_notify();
bool must_block_host = 0;
bool unsupport_host = 0;
if (o_notify)
must_block_host = is_blocked(o_notify, NOTIFY_BLOCK_TYPE_HOST);
pr_info("%s : enable=%d, must_block_host=%d\n",
__func__, enable, must_block_host);
if (enable) {
if (o_notify)
unsupport_host = !is_usb_host(o_notify);
pr_info("%s : unsupport_host=%d\n", __func__, unsupport_host);
if (must_block_host || unsupport_host) {
enable = false;
pr_info("%s : turn off vbus because of blocked host\n",
__func__);
}
} else {
// don't turn off because of blocked (already off)
if (must_block_host)
return;
}
#endif
pr_info("%s %d, enable=%d\n", __func__, __LINE__, enable);
psy_otg = get_power_supply_by_name("otg");
if (psy_otg) {
val.intval = enable;
ret = psy_otg->desc->set_property(psy_otg, POWER_SUPPLY_PROP_ONLINE, &val);
} else {
pr_err("%s: Fail to get psy battery\n", __func__);
}
if (ret) {
pr_err("%s: fail to set power_suppy ONLINE property(%d)\n",
__func__, ret);
} else {
pr_info("otg accessory power = %d\n", on);
}
}
static int s2mm005_src_capacity_information(const struct i2c_client *i2c, uint32_t *RX_SRC_CAPA_MSG,
PDIC_SINK_STATUS * pd_sink_status, uint8_t *do_power_nego)
{
uint32_t RdCnt;
uint32_t PDO_cnt;
uint32_t PDO_sel;
int available_pdo_num = 0;
int num_of_obj = 0;
MSG_HEADER_Type *MSG_HDR;
SRC_FIXED_SUPPLY_Typedef *MSG_FIXED_SUPPLY;
SRC_VAR_SUPPLY_Typedef *MSG_VAR_SUPPLY;
SRC_BAT_SUPPLY_Typedef *MSG_BAT_SUPPLY;
dev_info(&i2c->dev, "\n");
for(RdCnt=0;RdCnt<8;RdCnt++)
{
dev_info(&i2c->dev, "Rd_SRC_CAPA_%d : 0x%X\n", RdCnt, RX_SRC_CAPA_MSG[RdCnt]);
}
MSG_HDR = (MSG_HEADER_Type *)&RX_SRC_CAPA_MSG[0];
dev_info(&i2c->dev, "=======================================\n");
dev_info(&i2c->dev, " MSG Header\n");
dev_info(&i2c->dev, " Rsvd_msg_header : %d\n",MSG_HDR->Rsvd_msg_header );
dev_info(&i2c->dev, " Number_of_obj : %d\n",MSG_HDR->Number_of_obj );
dev_info(&i2c->dev, " Message_ID : %d\n",MSG_HDR->Message_ID );
dev_info(&i2c->dev, " Port_Power_Role : %d\n",MSG_HDR->Port_Power_Role );
dev_info(&i2c->dev, " Specification_Revision : %d\n",MSG_HDR->Specification_Revision );
dev_info(&i2c->dev, " Port_Data_Role : %d\n",MSG_HDR->Port_Data_Role );
dev_info(&i2c->dev, " Rsvd2_msg_header : %d\n",MSG_HDR->Rsvd2_msg_header );
dev_info(&i2c->dev, " Message_Type : %d\n",MSG_HDR->Message_Type );
num_of_obj = MSG_HDR->Number_of_obj > MAX_PDO_NUM ? MAX_PDO_NUM : MSG_HDR->Number_of_obj;
for (PDO_cnt = 0; PDO_cnt < num_of_obj; PDO_cnt++)
{
PDO_sel = (RX_SRC_CAPA_MSG[PDO_cnt + 1] >> 30) & 0x3;
dev_info(&i2c->dev, " =================\n");
dev_info(&i2c->dev, " PDO_Num : %d\n", (PDO_cnt + 1));
if(PDO_sel == 0) // *MSG_FIXED_SUPPLY
{
MSG_FIXED_SUPPLY = (SRC_FIXED_SUPPLY_Typedef *)&RX_SRC_CAPA_MSG[PDO_cnt + 1];
if(MSG_FIXED_SUPPLY->Voltage_Unit <= (AVAILABLE_VOLTAGE/UNIT_FOR_VOLTAGE))
available_pdo_num = PDO_cnt + 1;
if (!(*do_power_nego) &&
(pd_sink_status->power_list[PDO_cnt+1].max_voltage != MSG_FIXED_SUPPLY->Voltage_Unit * UNIT_FOR_VOLTAGE ||
pd_sink_status->power_list[PDO_cnt+1].max_current != MSG_FIXED_SUPPLY->Maximum_Current * UNIT_FOR_CURRENT))
*do_power_nego = 1;
pd_sink_status->power_list[PDO_cnt+1].max_voltage = MSG_FIXED_SUPPLY->Voltage_Unit * UNIT_FOR_VOLTAGE;
pd_sink_status->power_list[PDO_cnt+1].max_current = MSG_FIXED_SUPPLY->Maximum_Current * UNIT_FOR_CURRENT;
dev_info(&i2c->dev, " PDO_Parameter(FIXED_SUPPLY) : %d\n",MSG_FIXED_SUPPLY->PDO_Parameter );
dev_info(&i2c->dev, " Dual_Role_Power : %d\n",MSG_FIXED_SUPPLY->Dual_Role_Power );
dev_info(&i2c->dev, " USB_Suspend_Support : %d\n",MSG_FIXED_SUPPLY->USB_Suspend_Support );
dev_info(&i2c->dev, " Externally_POW : %d\n",MSG_FIXED_SUPPLY->Externally_POW );
dev_info(&i2c->dev, " USB_Comm_Capable : %d\n",MSG_FIXED_SUPPLY->USB_Comm_Capable );
dev_info(&i2c->dev, " Data_Role_Swap : %d\n",MSG_FIXED_SUPPLY->Data_Role_Swap );
dev_info(&i2c->dev, " Reserved : %d\n",MSG_FIXED_SUPPLY->Reserved );
dev_info(&i2c->dev, " Peak_Current : %d\n",MSG_FIXED_SUPPLY->Peak_Current );
dev_info(&i2c->dev, " Voltage_Unit : %d\n",MSG_FIXED_SUPPLY->Voltage_Unit );
dev_info(&i2c->dev, " Maximum_Current : %d\n",MSG_FIXED_SUPPLY->Maximum_Current );
}
else if(PDO_sel == 2) // *MSG_VAR_SUPPLY
{
MSG_VAR_SUPPLY = (SRC_VAR_SUPPLY_Typedef *)&RX_SRC_CAPA_MSG[PDO_cnt + 1];
dev_info(&i2c->dev, " PDO_Parameter(VAR_SUPPLY) : %d\n",MSG_VAR_SUPPLY->PDO_Parameter );
dev_info(&i2c->dev, " Maximum_Voltage : %d\n",MSG_VAR_SUPPLY->Maximum_Voltage );
dev_info(&i2c->dev, " Minimum_Voltage : %d\n",MSG_VAR_SUPPLY->Minimum_Voltage );
dev_info(&i2c->dev, " Maximum_Current : %d\n",MSG_VAR_SUPPLY->Maximum_Current );
}
else if(PDO_sel == 1) // *MSG_BAT_SUPPLY
{
MSG_BAT_SUPPLY = (SRC_BAT_SUPPLY_Typedef *)&RX_SRC_CAPA_MSG[PDO_cnt + 1];
dev_info(&i2c->dev, " PDO_Parameter(BAT_SUPPLY) : %d\n",MSG_BAT_SUPPLY->PDO_Parameter );
dev_info(&i2c->dev, " Maximum_Voltage : %d\n",MSG_BAT_SUPPLY->Maximum_Voltage );
dev_info(&i2c->dev, " Minimum_Voltage : %d\n",MSG_BAT_SUPPLY->Minimum_Voltage );
dev_info(&i2c->dev, " Maximum_Allow_Power : %d\n",MSG_BAT_SUPPLY->Maximum_Allow_Power );
}
}
/* the number of available pdo list */
pd_sink_status->available_pdo_num = available_pdo_num;
dev_info(&i2c->dev, "=======================================\n");
return available_pdo_num;
}
////////////////////////////////////////////////////////////////////////////////
// Processing message role
////////////////////////////////////////////////////////////////////////////////
void process_message_role(void *data)
{
struct s2mm005_data *usbpd_data = data;
struct otg_notify *o_notify = get_otg_notify();
int is_dfp = 0;
int is_src = 0;
// 1. read pd state
is_dfp = usbpd_data->func_state & (0x1 << 26) ? 1 : 0;
is_src = usbpd_data->func_state & (0x1 << 25) ? 1 : 0;
pr_info("%s func_state :0x%X, is_dfp : %d, is_src : %d\n", __func__,
usbpd_data->func_state, is_dfp, is_src);
pr_info("%s current port data_role : %d, power_role : %d\n", __func__,
usbpd_data->typec_data_role, usbpd_data->typec_power_role);
if (is_src == usbpd_data->typec_power_role) {
pr_info("%s skip. already power role is set.\n", __func__);
return;
}
#ifdef CONFIG_MUIC_SM5705_SWITCH_CONTROL_GPIO
pr_info("%s call muic_GPIO_control(1) to keep usb path\n", __func__);
muic_GPIO_control(1);
#endif
// 2. process power role
if (usbpd_data->func_state != State_PE_PRS_SNK_SRC_Source_on) {
#if defined(CONFIG_DUAL_ROLE_USB_INTF)
if (is_src && (usbpd_data->power_role == DUAL_ROLE_PROP_PR_SNK)) {
ccic_event_work(usbpd_data, CCIC_NOTIFY_DEV_BATTERY,
CCIC_NOTIFY_ID_ATTACH, 0, 0, 0);
}
#elif defined (CONFIG_TYPEC)
if (is_src && (usbpd_data->typec_power_role == TYPEC_SINK)) {
pd_noti.event = PDIC_NOTIFY_EVENT_PD_PRSWAP_SNKTOSRC;
pd_noti.sink_status.selected_pdo_num = 0;
pd_noti.sink_status.available_pdo_num = 0;
pd_noti.sink_status.current_pdo_num = 0;
ccic_event_work(usbpd_data, CCIC_NOTIFY_DEV_BATTERY,
CCIC_NOTIFY_ID_POWER_STATUS, 0, 0, 0);
}
#endif
}
#if defined(CONFIG_USB_HOST_NOTIFY)
if (is_src)
send_otg_notify(o_notify, NOTIFY_EVENT_POWER_SOURCE, 1);
else
send_otg_notify(o_notify, NOTIFY_EVENT_POWER_SOURCE, 0);
#endif
vbus_turn_on_ctrl(is_src);
#if defined(CONFIG_DUAL_ROLE_USB_INTF)
usbpd_data->power_role = is_src ?
DUAL_ROLE_PROP_PR_SRC : DUAL_ROLE_PROP_PR_SNK;
ccic_event_work(usbpd_data, CCIC_NOTIFY_DEV_PDIC,
CCIC_NOTIFY_ID_ROLE_SWAP, 0, 0, 0);
#elif defined (CONFIG_TYPEC)
usbpd_data->typec_power_role = is_src ? TYPEC_SOURCE : TYPEC_SINK;
typec_set_pwr_role(usbpd_data->port, usbpd_data->typec_power_role);
#endif
if (usbpd_data->typec_try_state_change == TRY_ROLE_SWAP_PR) {
pr_info("%s : power role is changed %s\n",
__func__, is_src ? "SOURCE" : "SINK");
usbpd_data->typec_try_state_change = TRY_ROLE_SWAP_NONE;
complete(&usbpd_data->typec_reverse_completion);
pr_info("%s typec_reverse_completion!\n", __func__);
}
}
void process_pd(void *data, u8 plug_attach_done, u8 *pdic_attach, MSG_IRQ_STATUS_Type *MSG_IRQ_State)
{
struct s2mm005_data *usbpd_data = data;
struct i2c_client *i2c = usbpd_data->i2c;
uint16_t REG_ADD;
uint8_t rp_currentlvl, is_src, i;
REQUEST_FIXED_SUPPLY_STRUCT_Typedef *request_power_number;
pr_info("%s\n",__func__);
if (usbpd_data->short_detected)
rp_currentlvl = RP_CURRENT_ABNORMAL;
else
rp_currentlvl = ((usbpd_data->func_state >> 27) & 0x3);
is_src = (usbpd_data->func_state & (0x1 << 25) ? 1 : 0);
dev_info(&i2c->dev, "rp_currentlvl:0x%02X, is_source:0x%02X\n", rp_currentlvl, is_src);
if (MSG_IRQ_State->BITS.Ctrl_Flag_PR_Swap)
{
usbpd_data->is_pr_swap++;
dev_info(&i2c->dev, "PR_Swap requested to %s, receive\n", is_src ? "SOURCE" : "SINK");
process_message_role(usbpd_data);
} else if (usbpd_data->typec_try_state_change == TRY_ROLE_SWAP_PR) {
dev_info(&i2c->dev, "PR_Swap requested to %s, send\n", is_src ? "SOURCE" : "SINK");
process_message_role(usbpd_data);
} else ;
if (MSG_IRQ_State->BITS.Data_Flag_SRC_Capability)
{
uint8_t ReadMSG[32];
int available_pdo_num;
uint8_t do_power_nego = 0;
pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK;
REG_ADD = REG_RX_SRC_CAPA_MSG;
s2mm005_read_byte(i2c, REG_ADD, ReadMSG, 32);
available_pdo_num = s2mm005_src_capacity_information(i2c, (uint32_t *)ReadMSG, &pd_noti.sink_status, &do_power_nego);
REG_ADD = REG_TX_REQUEST_MSG;
s2mm005_read_byte(i2c, REG_ADD, ReadMSG, 32);
request_power_number = (REQUEST_FIXED_SUPPLY_STRUCT_Typedef *)&ReadMSG[4];
pr_info(" %s : Object_posision(%d), available_pdo_num(%d), selected_pdo_num(%d) \n", __func__,
request_power_number->Object_Position, available_pdo_num, pd_noti.sink_status.selected_pdo_num);
pd_noti.sink_status.current_pdo_num = request_power_number->Object_Position;
if(available_pdo_num > 0)
{
if(request_power_number->Object_Position != pd_noti.sink_status.selected_pdo_num)
{
if (pd_noti.sink_status.selected_pdo_num == 0)
{
pr_info(" %s : PDO is not selected yet by default\n", __func__);
pd_noti.sink_status.selected_pdo_num = pd_noti.sink_status.current_pdo_num;
}
} else {
if (do_power_nego) {
pr_info(" %s : PDO(%d) is selected, but power negotiation is requested\n",
__func__, pd_noti.sink_status.selected_pdo_num);
pd_noti.sink_status.selected_pdo_num = 0;
pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK_CAP;
} else {
pr_info(" %s : PDO(%d) is selected, but same with previous list, so skip\n",
__func__, pd_noti.sink_status.selected_pdo_num);
}
}
*pdic_attach = 1;
} else {
pr_info(" %s : PDO is not selected\n", __func__);
}
}
if (MSG_IRQ_State->BITS.Ctrl_Flag_Get_Sink_Cap)
{
pr_info(" %s : SRC requested SINK Cap\n", __func__);
}
/* notify to battery */
#ifdef CONFIG_USB_TYPEC_MANAGER_NOTIFIER
if (plug_attach_done) {
if (*pdic_attach) {
/* PD charger is detected by PDIC */
} else if (!is_src && (usbpd_data->pd_state == State_PE_SNK_Wait_for_Capabilities ||
usbpd_data->pd_state == State_ErrorRecovery) &&
rp_currentlvl != pd_noti.sink_status.rp_currentlvl &&
rp_currentlvl >= RP_CURRENT_LEVEL_DEFAULT) {
if (rp_currentlvl == RP_CURRENT_LEVEL3) {
/* 5V/3A RP charger is detected by CCIC */
pd_noti.sink_status.rp_currentlvl = RP_CURRENT_LEVEL3;
pd_noti.event = PDIC_NOTIFY_EVENT_CCIC_ATTACH;
} else if (rp_currentlvl == RP_CURRENT_LEVEL2) {
/* 5V/1.5A RP charger is detected by CCIC */
pd_noti.sink_status.rp_currentlvl = RP_CURRENT_LEVEL2;
pd_noti.event = PDIC_NOTIFY_EVENT_CCIC_ATTACH;
} else if (rp_currentlvl == RP_CURRENT_LEVEL_DEFAULT) {
/* 5V/0.5A RP charger is detected by CCIC */
pd_noti.sink_status.rp_currentlvl = RP_CURRENT_LEVEL_DEFAULT;
pd_noti.event = PDIC_NOTIFY_EVENT_CCIC_ATTACH;
} else if (rp_currentlvl == RP_CURRENT_ABNORMAL) {
/* ABNORMAL RP charger is detected by CCIC */
pd_noti.sink_status.rp_currentlvl = RP_CURRENT_ABNORMAL;
pd_noti.event = PDIC_NOTIFY_EVENT_CCIC_ATTACH;
} else
return;
} else
return;
#ifdef CONFIG_SEC_FACTORY
pr_info(" %s : debug pdic_attach(%d) event(%d)\n", __func__, *pdic_attach, pd_noti.event);
#endif
ccic_event_work(usbpd_data, CCIC_NOTIFY_DEV_BATTERY, CCIC_NOTIFY_ID_POWER_STATUS, *pdic_attach, 0, 0);
} else {
for (i = 0; i < MAX_PDO_NUM + 1; i++) {
pd_noti.sink_status.power_list[i].max_current = 0;
pd_noti.sink_status.power_list[i].max_voltage = 0;
}
pd_noti.sink_status.rp_currentlvl = RP_CURRENT_LEVEL_NONE;
pd_noti.sink_status.available_pdo_num = 0;
pd_noti.sink_status.selected_pdo_num = 0;
pd_noti.sink_status.current_pdo_num = 0;
pd_noti.event = PDIC_NOTIFY_EVENT_DETACH;
}
#else
if(plug_attach_done)
{
/* PD notify */
if(*pdic_attach)
pd_noti.event = PDIC_NOTIFY_EVENT_PD_SINK;
else
pd_noti.event = PDIC_NOTIFY_EVENT_CCIC_ATTACH;
}
else
{
pd_noti.sink_status.selected_pdo_num = 0;
pd_noti.event = PDIC_NOTIFY_EVENT_DETACH;
}
pdic_notifier_call(pd_noti);
#endif
}

View File

@@ -340,6 +340,8 @@ struct fastrpc_apps {
bool secure_flag;
spinlock_t ctxlock;
struct smq_invoke_ctx *ctxtable[FASTRPC_CTX_MAX];
int tx_counter;
int rx_counter;
};
struct fastrpc_mmap {
@@ -1360,6 +1362,8 @@ static void context_free(struct smq_invoke_ctx *ctx)
if (me->ctxtable[i] == ctx) {
handle = me->ctxtable[i]->handle;
ptr = me->ctxtable[i]->ptr;
if(ctx->fl->cid == 0x2)
me->tx_counter++;
me->ctxtable[i] = NULL;
break;
}
@@ -2065,6 +2069,7 @@ static int fastrpc_internal_invoke(struct fastrpc_file *fl, uint32_t mode,
int err = 0, cid = -1, interrupted = 0;
struct timespec invoket = {0};
int64_t *perf_counter = NULL;
struct fastrpc_apps *me = &gfa;
cid = fl->cid;
VERIFY(err, cid >= ADSP_DOMAIN_ID && cid < NUM_CHANNELS);
@@ -3138,6 +3143,8 @@ static void fastrpc_glink_notify_rx(void *handle, const void *priv,
spin_unlock_irqrestore(&me->ctxlock, irq_flags);
goto bail;
}
if (me->ctxtable[index]->fl->cid == 0x2)
me->rx_counter++;
me->ctxtable[index]->handle = handle;
me->ctxtable[index]->ptr = ptr;
spin_unlock_irqrestore(&me->ctxlock, irq_flags);
@@ -3417,6 +3424,8 @@ static ssize_t fastrpc_debugfs_read(struct file *filp, char __user *buffer,
char *fileinfo = NULL;
char single_line[UL_SIZE] = "----------------";
char title[UL_SIZE] = "=========================";
single_line[UL_SIZE-1]='\0';
title[UL_SIZE-1]='\0';
fileinfo = kzalloc(DEBUGFS_SIZE, GFP_KERNEL);
if (!fileinfo)
@@ -4704,6 +4713,8 @@ static int __init fastrpc_device_init(void)
me->dev = NULL;
me->glink = true;
me->secure_flag = false;
me->tx_counter = 0;
me->rx_counter = 0;
VERIFY(err, 0 == platform_driver_register(&fastrpc_driver));
if (err)
goto register_bail;

View File

@@ -30,6 +30,9 @@
#include <linux/pm_opp.h>
#include <linux/regulator/consumer.h>
#include <linux/sec_debug.h>
#include <trace/events/power.h>
#include "clk.h"
#if defined(CONFIG_COMMON_CLK)
@@ -94,6 +97,8 @@ struct clk_core {
unsigned long *rate_max;
int num_rate_max;
};
extern unsigned int sec_debug_level(void);
bool is_dbg_level_low;
#define CREATE_TRACE_POINTS
#include <trace/events/clk.h>
@@ -1811,6 +1816,8 @@ static int clk_change_rate(struct clk_core *core)
}
trace_clk_set_rate(core, core->new_rate);
sec_debug_clock_rate_log(core->name, core->new_rate,
raw_smp_processor_id());
/* Enforce vdd requirements for new frequency. */
if (core->prepare_count) {
@@ -1847,6 +1854,8 @@ static int clk_change_rate(struct clk_core *core)
}
trace_clk_set_rate_complete(core, core->new_rate);
sec_debug_clock_rate_complete_log(core->name, core->new_rate,
raw_smp_processor_id());
/* Release vdd requirements for old frequency. */
if (core->prepare_count)
@@ -2365,7 +2374,7 @@ EXPORT_SYMBOL_GPL(clk_list_frequency);
static struct dentry *rootdir;
static int inited = 0;
static u32 debug_suspend;
static u32 debug_suspend = 1;
static DEFINE_MUTEX(clk_debug_lock);
static HLIST_HEAD(clk_debug_list);
@@ -3077,6 +3086,12 @@ static int __init clk_debug_init(void)
rootdir = debugfs_create_dir("clk", NULL);
//ANDROID_DEBUG_LEVEL_LOW 0x4f4c
if (sec_debug_level() == 0x4f4c)
is_dbg_level_low = true;
else
is_dbg_level_low = false;
if (!rootdir)
return -ENOMEM;

View File

@@ -36,6 +36,8 @@
#include <dt-bindings/clock/qcom,cpucc-sdm845.h>
#include <dt-bindings/regulator/qcom,rpmh-regulator.h>
#include <linux/sec_debug_partition.h>
#include "common.h"
#include "clk-regmap.h"
#include "clk-voter.h"
@@ -296,6 +298,59 @@ static int clk_cpu_determine_rate(struct clk_hw *hw,
return 0;
}
#ifdef CONFIG_SEC_DEBUG_APPS_CLK_LOGGING
typedef struct {
uint64_t ktime;
uint64_t qtime;
uint64_t rate;
} apps_clk_log_t;
#define MAX_CLK_LOG_CNT (10)
typedef struct {
uint32_t max_cnt;
uint32_t index;
apps_clk_log_t log[MAX_CLK_LOG_CNT];
} cpuclk_log_t;
cpuclk_log_t cpuclk_log[3] = {
[0] = {.max_cnt = MAX_CLK_LOG_CNT,},
[1] = {.max_cnt = MAX_CLK_LOG_CNT,},
[2] = {.max_cnt = MAX_CLK_LOG_CNT,},
};
static void clk_osm_add_log(struct cpufreq_policy *policy, unsigned int index)
{
struct clk_osm *c = policy->driver_data;
cpuclk_log_t *clk = NULL;
apps_clk_log_t *log = NULL;
uint64_t idx = 0;
uint32_t cluster = 0;
cluster = policy->cpu / 4;
if (!WARN(cluster >= 2, "%s : invalid cluster_num(%u), dbg_name(%s)\n",
__func__, cluster, clk_hw_get_name(&c->hw))) {
if (cluster == 0)
clk = &cpuclk_log[PWR_CLUSTER];
else
clk = &cpuclk_log[PERF_CLUSTER];
idx = clk->index;
log = &clk->log[idx];
log->ktime = local_clock();
log->qtime = arch_counter_get_cntvct();
log->rate = policy->freq_table[index].frequency;
clk->index = (clk->index + 1) % MAX_CLK_LOG_CNT;
}
}
void *clk_osm_get_log_addr(void)
{
return (void *)&cpuclk_log;
}
EXPORT_SYMBOL(clk_osm_get_log_addr);
#endif
static int l3_clk_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
@@ -350,6 +405,22 @@ static int l3_clk_set_rate(struct clk_hw *hw, unsigned long rate,
return -ETIMEDOUT;
}
cpuclk->rate = rate;
#ifdef CONFIG_SEC_DEBUG_APPS_CLK_LOGGING
{
cpuclk_log_t *clk = NULL;
apps_clk_log_t *log = NULL;
uint64_t idx = 0;
clk = &cpuclk_log[L3];
idx = clk->index;
log = &clk->log[idx];
log->ktime = local_clock();
log->qtime = arch_counter_get_cntvct();
log->rate = rate;
clk->index = (clk->index + 1) % MAX_CLK_LOG_CNT;
}
#endif
return 0;
}
@@ -715,6 +786,9 @@ osm_cpufreq_target_index(struct cpufreq_policy *policy, unsigned int index)
struct clk_osm *c = policy->driver_data;
osm_set_index(c, index);
#ifdef CONFIG_SEC_DEBUG_APPS_CLK_LOGGING
clk_osm_add_log(policy, index);
#endif
return 0;
}

View File

@@ -201,6 +201,9 @@ static struct clk_rcg2 disp_cc_mdss_byte1_clk_src = {
};
static const struct freq_tbl ftbl_disp_cc_mdss_dp_aux_clk_src[] = {
#if defined(CONFIG_DISPLAY_SAMSUNG)
F(12800000, P_BI_TCXO, 1.5, 0, 0),
#endif
F(19200000, P_BI_TCXO, 1, 0, 0),
{ }
};

View File

@@ -966,13 +966,14 @@ static struct clk_rcg2 gcc_sdcc1_apps_clk_src = {
};
static const struct freq_tbl ftbl_gcc_sdcc2_apps_clk_src[] = {
F(300000, P_BI_TCXO, 32, 1, 2),
F(400000, P_BI_TCXO, 12, 1, 4),
F(9600000, P_BI_TCXO, 2, 0, 0),
F(19200000, P_BI_TCXO, 1, 0, 0),
F(25000000, P_GPLL0_OUT_EVEN, 12, 0, 0),
F(50000000, P_GPLL0_OUT_EVEN, 6, 0, 0),
F(100000000, P_GPLL0_OUT_MAIN, 6, 0, 0),
F(201500000, P_GPLL4_OUT_MAIN, 4, 0, 0),
F(179100000, P_GPLL4_OUT_MAIN, 4.5, 0, 0),
{ }
};

View File

@@ -163,8 +163,14 @@ static int qti_ice_setting_config(struct request *req,
setting->encr_bypass = false;
break;
case READ:
if (!ice_fde_flag || (ice_fde_flag & QCOM_ICE_DECRYPT))
if (!ice_fde_flag || (ice_fde_flag & QCOM_ICE_DECRYPT)) {
if (unlikely(req->cmd_flags & REQ_BYPASS)) {
setting->encr_bypass = true;
setting->decr_bypass = true;
} else {
setting->decr_bypass = false;
}
}
break;
default:
/* Should I say BUG_ON */

238
drivers/debug/Kconfig Normal file
View File

@@ -0,0 +1,238 @@
# When adding new entries keep the list in alphabetical order
comment "Samsung Debugging Feature"
menuconfig SEC_DEBUG
bool "Samsung TN Ramdump Feature"
default n
help
Samsung TN Ramdump Feature.
Enables collection of ram dump.
Enables task history, debug level etc.
This feature is enabled in defconfig.
if SEC_DEBUG
config SEC_DEBUG_PRINTK_NOCACHE
bool "Samsung non-cached kernel printk"
default y
help
Samsung non-cached kernel printk
This ensures that printk is never stale
which is a good feature when debugging without
hardware debuggers. If unsure, keep it turned on.
config SEC_DEBUG_SCHED_LOG
bool "Samsung Scheduler Logging Feature"
default n
help
Samsung Scheduler Logging Feature for Debug use.
Enables task scheduling history.
Enables IRQ scheduling history.
This feature is enabled in defconfig.
config SEC_DEBUG_SEMAPHORE_LOG
bool "Samsung Semaphore Logging Feature"
default n
help
Samsung Semaphore Logging Feature for Debug use.
Enables semaphore debugging statistics.
Enables logging.
This feature is enabled in defconfig.
config SEC_FILE_LEAK_DEBUG
bool "Samsung File Leak Debugging Feature"
default n
help
Samsung File Leak Debugging Feature for Debug use.
Enables the forced panic mode when EMFILE Eror occurs.
This feature is enabled in defconfig.
config SEC_LOGGER_BUFFER_EXPANSION
bool "Samsung Logger Buffer Expansion Feature"
default n
help
This is used to expand buffers of logger.
This feature is enabled in defconfig.
config SEC_LOGGER_BUFFER_EXPANSION_SIZE
int "Samsung Logger Buffer Expansion Size(MB)"
depends on SEC_LOGGER_BUFFER_EXPANSION
default 2
help
This is used to expand main buffer of logger(MB).
config SEC_DEBUG_USER
bool "Panic on Userspace fault"
default y
help
Panic on Userspace fault
This feature enables collection of ram dump,
on user fault.
Enabled native code debugging.
config SEC_DEBUG_IRQ_EXIT_LOG
bool "Temporary Logging for IRQ delay"
default n
help
Verbose Logging for IRQ delay.
Helps indetification of irq enter and exit.
This is to track the current state of IRQ execution.
This is enabled in defconfig file.
config SEC_DEBUG_MSG_LOG
bool "Message Log for ram dump debug"
default n
help
Verbose Logging for ram dump analysis.
Collects kernel debug log.
Log is collected in the no-cache area.
This feature is enabled in defconfig.
config SEC_DEBUG_SUMMARY
bool "Debug summary"
depends on SEC_DEBUG_SCHED_LOG
default n
help
Subsystems debug summary feature.
When enabled provides kernel logs, modem logs, RPM registers,
Schedule and IRQ logs at the time of crash along with the
reason for crash, which can be extracted as a html in RAM dump mode.
config SEC_DEBUG_DCVS_LOG
bool "Temporary Logging for DCVS"
default n
help
DCVS Logging Feature for Debug use.
The ACPU clock rate changes will be logged as a part
of secdbg_log structure along with the CPU time stamp.
The previous frequency and the new frequency for both the CPU along
with the CPU time stamp will be logged.
config SEC_DEBUG_POWER_LOG
bool "Temporary Logging for MSM POWER"
default n
help
POWER Logging Feature for Debug use.
The power and clock gating will be logged as a part
of secdbg_log structure along with the CPU time stamp.
config SEC_DEBUG_FUELGAUGE_LOG
bool "Temporary Logging for FuelGauge"
default n
help
FuelGauge Logging Feature for Debug use.
The FuelGauge values are logged as a part
of secdbg_log structure along with the CPU time stamp.
The voltage and soc values along with the CPU time will be logged.
config SEC_DEBUG_LOW_LOG
bool "Kernel Message Logging for Debug Level Low"
default n
help
Kernel Message Logging Feature for Debug use.
The Kernel Messages are logged using file I/O
when an exception occurs, when the debug level is low.
The file I/O is added in kernel driver level
so that kernel messages are logged on next reboot.
config SEC_DEBUG_MDM_FILE_INFO
bool "MDM filename and line number in UPLOAD mode"
default n
help
This feature enables display of MDM info in upload mode.
This feature enabled SSR in debug level low.
Collects MDM ram dump and then calls panic.
UPLOAD mode has MDM dump info to show it on LCD.
config SEC_DEBUG_DOUBLE_FREE
bool "Enable double free detection"
default n
help
Detect erraneous codes that frees a kmalloced node
twice. When kfree(p) is called, p is not freed right away.
Instead, it is pushed into a circular buffer. What it frees
is the oldest pointer that was pushed into the buffer.
If someone tries to free the same pointer p, *p
is read and checked for a magic code that is written
when it was first freed. If it matches, the whole
circular buffer is searched. Panic is be called when
the match is found.
config SEC_DEBUG_FORCE_ERROR
bool "enable force error"
default n
help
This option enable to force error by sysfs
config SEC_DEBUG_SEC_WDOG_BITE
bool "Samsung fore secure bite simulation"
default n
depends on QCOM_SCM && SEC_DEBUG_FORCE_ERROR
help
simulation for secure bite.
This feature is enabled in defconfig.
config SEC_LOG_LAST_KMSG
bool "Enable /proc/last_kmsg support: if EMBEDDED"
default n
help
This option enables /proc/last_kmsg support.
config SEC_DEBUG_NOCACHE_LOG_IN_LEVEL_LOW
bool "Enable nocache logging in debug level LOW"
default n
help
Enable nocache logging in debug level LOW.
config SEC_SSR_DEBUG_LEVEL_CHK
bool "PERIPHERAL SECURE check"
default n
help
To check the authentication of peripheral image.
config USER_RESET_DEBUG
bool "reset reason debug feature in user version"
default n
help
This option provides reset history log in user version.
This option enable proc/reset_reason support
config USER_RESET_DEBUG_TEST
bool "reset reason debug test feature in eng version"
depends on USER_RESET_DEBUG
default n
help
This option enable for test in eng version(KP, DP, TP, WP)
config SEC_PERIPHERAL_SECURE_CHK
bool "PERIPHERAL SECURE check"
default n
depends on MSM_PIL
help
This option enables checking the authentication of peripheral image.
config SEC_DEBUG_PWDT
bool "Platform Watchdog check"
default n
help
To check Platform Watchdog thread status
endif
config SEC_QUEST
bool "Samsung QUEST Feature"
default n
help
Samsung QUEST Feature, to test chipset quality
config SEC_QUEST_UEFI
bool "Samsung QUEST UEFI Feature"
default n
help
Samsung QUEST UEFI Feature, to test chipset quality
config SEC_SKP
bool "Samsung SKP Feature"
default n
help
Samsung SKP Feature, to test chipset quality

14
drivers/debug/Makefile Normal file
View File

@@ -0,0 +1,14 @@
# When adding new entries keep the list in alphabetical order
obj-$(CONFIG_SEC_DEBUG) += sec_debug.o sec_kcompat.o \
sec_crashkey.o \
sec_crashkey_long.o \
sec_key_notifier.o \
sec_debug_partition.o
obj-$(CONFIG_SEC_DEBUG_SCHED_LOG) += sec_debug_sched_log.o
obj-$(CONFIG_USER_RESET_DEBUG) += sec_debug_user_reset.o
obj-$(CONFIG_SEC_DEBUG_FORCE_ERROR) += sec_debug_force_err.o
obj-$(CONFIG_SEC_DEBUG_DOUBLE_FREE) += sec_debug-dfd.o
obj-$(CONFIG_SEC_DEBUG_SUMMARY) += sec_debug_summary.o
obj-$(CONFIG_SEC_QUEST) += sec_quest.o
obj-$(CONFIG_SEC_QUEST) += spinlock_test.o

View File

@@ -0,0 +1,259 @@
// SPDX-License-Identifier: GPL-2.0
/*
* drivers/samsung/debug/sec_crashkey.c
*
* COPYRIGHT(C) 2019 Samsung Electronics Co., Ltd. All Right Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only 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.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/notifier.h>
#include <linux/of.h>
#include <linux/ratelimit.h>
#include <linux/slab.h>
#include <linux/sec_debug.h>
#include "sec_debug_internal.h"
#include "sec_key_notifier.h"
#define EVENT_KEY_PRESS(__keycode) \
{ .keycode = __keycode, .down = true, }
#define EVENT_KEY_RELEASE(__keycode) \
{ .keycode = __keycode, .down = false, }
#define EVENT_KEY_PRESS_AND_RELEASE(__keycode) \
EVENT_KEY_PRESS(__keycode), \
EVENT_KEY_RELEASE(__keycode)
struct event_pattern {
unsigned int keycode;
bool down;
};
struct event_state {
struct ratelimit_state rs;
struct event_pattern *desired_pattern;
struct event_pattern *received_pattern;
size_t nr_pattern;
const char *msg;
int interval;
size_t sequence;
};
static struct event_pattern crashkey_pattern[] = {
EVENT_KEY_PRESS(KEY_VOLUMEDOWN),
EVENT_KEY_PRESS_AND_RELEASE(KEY_POWER),
EVENT_KEY_PRESS_AND_RELEASE(KEY_POWER),
};
static struct event_pattern received_pattern[ARRAY_SIZE(crashkey_pattern)];
static struct event_state crashkey_state = {
.desired_pattern = crashkey_pattern,
.received_pattern = received_pattern,
.nr_pattern = ARRAY_SIZE(crashkey_pattern),
.msg = UPLOAD_MSG_CRASH_KEY,
.interval = 1 * HZ,
};
static struct event_pattern crashkey_user_pattern[] = {
EVENT_KEY_PRESS(KEY_VOLUMEDOWN), /* initial trigger */
EVENT_KEY_PRESS_AND_RELEASE(KEY_POWER), /* 1 */
EVENT_KEY_PRESS_AND_RELEASE(KEY_POWER), /* 2 */
EVENT_KEY_PRESS_AND_RELEASE(KEY_POWER), /* 3 */
EVENT_KEY_PRESS_AND_RELEASE(KEY_POWER), /* 4 */
EVENT_KEY_PRESS_AND_RELEASE(KEY_POWER), /* 5 */
EVENT_KEY_PRESS_AND_RELEASE(KEY_POWER), /* 6 */
EVENT_KEY_PRESS_AND_RELEASE(KEY_POWER), /* 7 */
EVENT_KEY_PRESS_AND_RELEASE(KEY_POWER), /* 8 */
EVENT_KEY_PRESS_AND_RELEASE(KEY_POWER), /* 9 */
EVENT_KEY_PRESS_AND_RELEASE(KEY_VOLUMEUP), /* 1 */
EVENT_KEY_PRESS_AND_RELEASE(KEY_VOLUMEUP), /* 2 */
EVENT_KEY_PRESS_AND_RELEASE(KEY_VOLUMEUP), /* 3 */
EVENT_KEY_PRESS_AND_RELEASE(KEY_VOLUMEUP), /* 4 */
EVENT_KEY_PRESS_AND_RELEASE(KEY_VOLUMEUP), /* 5 */
EVENT_KEY_PRESS_AND_RELEASE(KEY_POWER), /* 1 */
EVENT_KEY_PRESS_AND_RELEASE(KEY_POWER), /* 2 */
EVENT_KEY_PRESS_AND_RELEASE(KEY_POWER), /* 3 */
};
static struct event_pattern received_user_pattern[ARRAY_SIZE(crashkey_user_pattern)];
static struct event_state crashkey_user_state = {
.desired_pattern = crashkey_user_pattern,
.received_pattern = received_user_pattern,
.nr_pattern = ARRAY_SIZE(crashkey_user_pattern),
.msg = UPLOAD_MSG_USER_CRASH_KEY,
.interval = 7 * HZ,
};
static struct event_state *key_event_state;
static __always_inline int __crashkey_test_pattern(size_t len)
{
return memcmp(key_event_state->desired_pattern,
key_event_state->received_pattern,
len * sizeof(struct event_pattern));
}
static __always_inline void __crashkey_clear_received_pattern(void)
{
key_event_state->sequence = 0;
memset(key_event_state->received_pattern, 0x0,
key_event_state->nr_pattern * sizeof(struct event_pattern));
}
static int sec_crashkey_notifier_call(struct notifier_block *this,
unsigned long type, void *data)
{
struct sec_key_notifier_param *param = data;
size_t idx = key_event_state->sequence;
if (idx >= key_event_state->nr_pattern)
goto clear_state;
key_event_state->received_pattern[idx].keycode = param->keycode;
key_event_state->received_pattern[idx].down = !!param->down;
key_event_state->sequence++;
if (!__ratelimit(&(key_event_state->rs))) {
if (!__crashkey_test_pattern(key_event_state->nr_pattern)) {
#ifdef CONFIG_SEC_USER_RESET_DEBUG
sec_debug_store_extc_idx(false);
#endif
panic(key_event_state->msg);
} else
goto clear_state;
} else if (__crashkey_test_pattern(key_event_state->sequence))
goto clear_state;
return NOTIFY_DONE;
clear_state:
__crashkey_clear_received_pattern();
return NOTIFY_DONE;
}
static struct notifier_block sec_crashkey_notifier = {
.notifier_call = sec_crashkey_notifier_call,
};
static unsigned int *carashkey_used_event;
static size_t crashkey_nr_used_event;
static int __init __crashkey_init_used_event(void)
{
bool is_new;
size_t i;
size_t j;
carashkey_used_event = kmalloc_array(key_event_state->nr_pattern,
sizeof(*carashkey_used_event), GFP_KERNEL);
if (!carashkey_used_event)
return -ENOMEM;
carashkey_used_event[0] =
key_event_state->desired_pattern[0].keycode;
crashkey_nr_used_event = 1;
for (i = 1; i < key_event_state->nr_pattern; i++) {
for (j = 0, is_new = true; j < crashkey_nr_used_event; j++) {
if (carashkey_used_event[j] ==
key_event_state->desired_pattern[i].keycode)
is_new = false;
}
if (is_new)
carashkey_used_event[crashkey_nr_used_event++] =
key_event_state->desired_pattern[i].keycode;
}
return 0;
}
static void __sec_crashkey_parse_dt_replace_keymap(void)
{
struct device_node *parent, *node;
int err;
size_t i;
u32 resin_keycode, pwr_keycode;
parent = of_find_node_by_path("/soc");
if (!parent)
goto no_dt;
node = of_find_node_by_name(parent, "sec_key_crash");
if (!node)
goto no_dt;
err = of_property_read_u32(node, "resin-keycode", &resin_keycode);
if (err)
goto no_dt;
err = of_property_read_u32(node, "pwr-keycode", &pwr_keycode);
if (err)
goto no_dt;
for (i = 0; i < key_event_state->nr_pattern; i++) {
struct event_pattern *desired_pattern =
&key_event_state->desired_pattern[i];
switch (desired_pattern->keycode) {
case KEY_VOLUMEDOWN:
if (resin_keycode != KEY_VOLUMEDOWN)
desired_pattern->keycode = resin_keycode;
break;
case KEY_POWER:
if (pwr_keycode != KEY_POWER)
desired_pattern->keycode = pwr_keycode;
break;
}
}
pr_info("use dt keymap");
return;
no_dt:
pr_info("use default keymap");
}
static int __init sec_crashkey_init(void)
{
int err;
if (!sec_debug_is_enabled())
key_event_state = &crashkey_user_state;
else
key_event_state = &crashkey_state;
__sec_crashkey_parse_dt_replace_keymap();
err = __crashkey_init_used_event();
if (err) {
pr_warn("crashkey can not be enabled! (%d)", err);
return err;
}
ratelimit_state_init(&(key_event_state->rs),
key_event_state->interval,
key_event_state->nr_pattern - 1);
sec_kn_register_notifier(&sec_crashkey_notifier,
carashkey_used_event, crashkey_nr_used_event);
return 0;
}
arch_initcall_sync(sec_crashkey_init);

View File

@@ -0,0 +1,206 @@
// SPDX-License-Identifier: GPL-2.0
/*
* drivers/samsung/debug/sec_crashkey_long.c
*
* COPYRIGHT(C) 2019 Samsung Electronics Co., Ltd. All Right Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only 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.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__
#include <linux/bitmap.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/notifier.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/version.h>
#include <linux/sec_debug.h>
#include <linux/sec_crashkey.h>
#include "sec_debug_internal.h"
#include "sec_key_notifier.h"
#include "sec_kcompat.h"
#define EXPIRE_MSEC 6600
static unsigned int crashkey_long_used_event[] = {
KEY_VOLUMEDOWN,
KEY_POWER,
};
static size_t crashkey_long_nr_used_event =
ARRAY_SIZE(crashkey_long_used_event);
struct long_pressed_event_state {
unsigned long *bitmap_recieved;
size_t sz_bitmap;
};
static struct long_pressed_event_state __long_key_state;
static struct long_pressed_event_state *long_key_state = &__long_key_state;
static inline void __reset_pon_s2_ctrl_reset(void)
{
qpnp_control_s2_reset_onoff(0);
udelay(1000);
qpnp_control_s2_reset_onoff(1);
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0)
static void sec_crashkey_long_timer_handler(struct timer_list *tl)
#else
static void sec_crashkey_long_timer_handler(unsigned long data)
#endif
{
__pr_err("*** Force trigger kernel panic before triggering hard reset ***\n");
__reset_pon_s2_ctrl_reset();
panic(UPLOAD_MSG_LONG_KEY_PRESS);
}
static struct timer_list sec_crashkey_long_tl;
static int sec_crashkey_long_notifier_call(struct notifier_block *this,
unsigned long type, void *data)
{
struct sec_key_notifier_param *param = data;
int i;
bool matching;
if (param->down)
set_bit(param->keycode, long_key_state->bitmap_recieved);
else
clear_bit(param->keycode, long_key_state->bitmap_recieved);
for (i = 0, matching = true; i < crashkey_long_nr_used_event; i++) {
if (!test_bit(crashkey_long_used_event[i], long_key_state->bitmap_recieved)) {
matching = false;
break;
}
}
if (matching) {
if (sec_debug_get_upload_cause() == UPLOAD_CAUSE_INIT)
sec_debug_set_upload_cause(UPLOAD_CAUSE_POWER_LONG_PRESS);
if (unlikely(sec_debug_is_enabled())) {
if (!timer_pending(&sec_crashkey_long_tl)) {
timer_setup(&sec_crashkey_long_tl,
sec_crashkey_long_timer_handler, 0);
mod_timer(&sec_crashkey_long_tl,
jiffies + msecs_to_jiffies(EXPIRE_MSEC));
pr_info("long key timer - start");
}
}
} else {
if (sec_debug_get_upload_cause() == UPLOAD_CAUSE_POWER_LONG_PRESS)
sec_debug_set_upload_cause(UPLOAD_CAUSE_INIT);
if (timer_pending(&sec_crashkey_long_tl)) {
del_timer(&sec_crashkey_long_tl);
pr_info("long key timer - cancel");
}
}
return NOTIFY_DONE;
}
static struct notifier_block sec_crashkey_long_notifier = {
.notifier_call = sec_crashkey_long_notifier_call,
};
/* for retail group's request */
#if defined(CONFIG_SEC_PM)
void do_keyboard_notifier(int onoff)
{
pr_info("%s: onoff(%d)\n", __func__, onoff);
if (onoff)
sec_kn_register_notifier(&sec_crashkey_long_notifier,
crashkey_long_used_event, crashkey_long_nr_used_event);
else
sec_kn_unregister_notifier(&sec_crashkey_long_notifier,
crashkey_long_used_event, crashkey_long_nr_used_event);
}
EXPORT_SYMBOL(do_keyboard_notifier);
#endif
static void __sec_crashkey_parse_dt_replace_keymap(void)
{
struct device_node *parent, *node;
int err;
size_t i;
u32 resin_keycode, pwr_keycode;
parent = of_find_node_by_path("/soc");
if (!parent)
goto no_dt;
node = of_find_node_by_name(parent, "sec_key_crash");
if (!node)
goto no_dt;
err = of_property_read_u32(node, "resin-keycode", &resin_keycode);
if (err)
goto no_dt;
err = of_property_read_u32(node, "pwr-keycode", &pwr_keycode);
if (err)
goto no_dt;
for (i = 0; i < crashkey_long_nr_used_event; i++) {
unsigned int *keycode = &crashkey_long_used_event[i];
switch (*keycode) {
case KEY_VOLUMEDOWN:
if (resin_keycode != KEY_VOLUMEDOWN)
*keycode = resin_keycode;
break;
case KEY_POWER:
if (pwr_keycode != KEY_POWER)
*keycode = pwr_keycode;
break;
}
}
pr_info("use dt keymap");
return;
no_dt:
pr_info("use default keymap");
}
static int sec_crashkey_long_init(void)
{
unsigned long *bitmap_recieved;
bitmap_recieved = bitmap_zalloc(KEY_MAX, GFP_KERNEL);
if (!bitmap_recieved) {
kfree(bitmap_recieved);
return -ENOMEM;
}
__sec_crashkey_parse_dt_replace_keymap();
long_key_state->bitmap_recieved = bitmap_recieved;
long_key_state->sz_bitmap =
BITS_TO_LONGS(KEY_MAX) * sizeof(unsigned long);
sec_kn_register_notifier(&sec_crashkey_long_notifier,
crashkey_long_used_event, crashkey_long_nr_used_event);
return 0;
}
arch_initcall_sync(sec_crashkey_long_init);

View File

@@ -0,0 +1,407 @@
/*
* Copyright (c) 2012-2013 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only 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.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/ctype.h>
#include <linux/mm.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/shrinker.h>
#include <linux/circ_buf.h>
static int dfd_enable(const char *val, struct kernel_param *kp);
module_param_call(enable, dfd_enable, NULL, NULL,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
/* Double free detector(dfd) can use lots of memory because
* it needs to hold on the freed slabs, otherwise
* the the freeing node will be put into the kmem cache
* of it's size and the slab allocator will soon re-allocate
* this slab when the slab of that size is requested.
* So to alleviate the pressure of the other shrinkers when
* there is a memory pressure, enable DFD_HAS_SHRINKER below.
*/
#define DFD_HAS_SHRINKER
#ifdef DFD_HAS_SHRINKER
/* Using DFD shrinker will keep the DFD buffer entries low
* but at a cost. The page allocator will often go into
* the slowpath and try to reclaim pages and eventually call
* the shrinker.
* If you want to avoid this overhead, enable below feature
* to completely flush out the circular buffer and disable the
* DFD when there is a memory pressure */
//#define DFD_SHRINKER_DISABLE_DFD_ON_MEM_PRESSURE
#endif
#define KFREE_HOOK_BYPASS_MASK 0x1
/* The average size of a slab object is about 256 bytes.
* 1<<15 number of slab objects take about 8MB to 10MB
* (This average was mesaured with a min slab size of 64) */
#define KFREE_CIRC_BUF_SIZE (1<<15)
#define KFREE_FREE_MAGIC 0x65655266
static int dfd_panic = 1;
static int dfd_disabled;
static DEFINE_SPINLOCK(dfd_list_lock);
struct dfd_node {
void *addr;
void *caller;
};
struct dfd_node_list {
int head;
int tail;
struct dfd_node entry[KFREE_CIRC_BUF_SIZE];
};
struct dfd_node_list dfd_node_list;
static int __init setup_dfd_panic_disable(char *str)
{
dfd_panic = 0;
return 1;
}
__setup("dfd_panic_disable", setup_dfd_panic_disable);
/* the caller must hold the dfd_list_lock */
static void *circ_buf_lookup(struct dfd_node_list *circ_buf, void *addr)
{
int i;
for (i = circ_buf->tail; i != circ_buf->head ;
i = (i + 1) & (KFREE_CIRC_BUF_SIZE - 1)) {
if (circ_buf->entry[i].addr == addr)
return &circ_buf->entry[i];
}
return NULL;
}
/* the caller must hold the dfd_list_lock and must check
* for the buffer status before calling */
static void *circ_buf_get(struct dfd_node_list *circ_buf)
{
void *entry;
entry = &circ_buf->entry[circ_buf->tail];
smp_rmb();
circ_buf->tail = (circ_buf->tail + 1) &
(KFREE_CIRC_BUF_SIZE - 1);
return entry;
}
/* the caller must hold the dfd_list_lock and must check
* for the buffer status before calling */
static void *circ_buf_put(struct dfd_node_list *circ_buf,
struct dfd_node *entry)
{
memcpy(&circ_buf->entry[circ_buf->head], entry, sizeof(*entry));
smp_wmb();
circ_buf->head = (circ_buf->head + 1) &
(KFREE_CIRC_BUF_SIZE - 1);
return entry;
}
static int dfd_flush(void)
{
struct dfd_node *pentry;
unsigned long cnt;
unsigned long flags;
spin_lock_irqsave(&dfd_list_lock, flags);
cnt = CIRC_CNT(dfd_node_list.head, dfd_node_list.tail,
KFREE_CIRC_BUF_SIZE);
spin_unlock_irqrestore(&dfd_list_lock, flags);
pr_debug("cnt=%lu\n", cnt);
do_flush:
while (cnt) {
void *tofree = NULL;
/* we want to keep the lock region as short as possible
* so we will re-read the buf count every loop */
spin_lock_irqsave(&dfd_list_lock, flags);
cnt = CIRC_CNT(dfd_node_list.head, dfd_node_list.tail,
KFREE_CIRC_BUF_SIZE);
if (cnt == 0) {
spin_unlock_irqrestore(&dfd_list_lock, flags);
break;
}
if ((pentry = circ_buf_get(&dfd_node_list)) != NULL)
tofree = pentry->addr;
spin_unlock_irqrestore(&dfd_list_lock, flags);
if (tofree)
kfree((void *)((unsigned long)tofree |
KFREE_HOOK_BYPASS_MASK));
cnt--;
}
spin_lock_irqsave(&dfd_list_lock, flags);
cnt = CIRC_CNT(dfd_node_list.head, dfd_node_list.tail,
KFREE_CIRC_BUF_SIZE);
spin_unlock_irqrestore(&dfd_list_lock, flags);
if (!dfd_disabled)
goto out;
if (cnt)
goto do_flush;
out:
return cnt;
}
static int dfd_enable(const char *val, struct kernel_param *kp)
{
if (!strncmp(val, "1", 1)) {
dfd_disabled = 0;
pr_info("double free detection is enabled\n");
} else if (!strncmp(val, "0", 1)) {
dfd_disabled = 1;
dfd_flush();
pr_info("double free detection is disabled\n");
}
return 0;
}
#ifdef DFD_HAS_SHRINKER
int dfd_shrink(struct shrinker *shrinker, struct shrink_control *sc)
{
#ifndef DFD_SHRINKER_DISABLE_DFD_ON_MEM_PRESSURE
struct dfd_node *pentry;
unsigned long nr = sc->nr_to_scan;
#endif
unsigned long flags;
unsigned long nr_objs;
spin_lock_irqsave(&dfd_list_lock, flags);
nr_objs = CIRC_CNT(dfd_node_list.head, dfd_node_list.tail,
KFREE_CIRC_BUF_SIZE);
spin_unlock_irqrestore(&dfd_list_lock, flags);
/* nothing to reclaim from here */
if (nr_objs == 0) {
nr_objs = -1;
goto out;
}
#ifdef DFD_SHRINKER_DISABLE_DFD_ON_MEM_PRESSURE
/* disable double free detection. This will flush
* the entire circular buffer out. */
dfd_disable();
#else
/* return max slab objects freeable */
if (nr == 0)
return nr_objs;
if (nr > nr_objs)
nr = nr_objs;
pr_debug("nr_objs=%lu\n", nr_objs);
while (nr) {
unsigned long cnt;
void *tofree = NULL;
spin_lock_irqsave(&dfd_list_lock, flags);
cnt = CIRC_CNT(dfd_node_list.head, dfd_node_list.tail,
KFREE_CIRC_BUF_SIZE);
if (cnt > 0) {
if ((pentry = circ_buf_get(&dfd_node_list)) != NULL)
tofree = pentry->addr;
}
spin_unlock_irqrestore(&dfd_list_lock, flags);
if (tofree)
kfree((void *)((unsigned long)tofree |
KFREE_HOOK_BYPASS_MASK));
nr--;
}
#endif
spin_lock_irqsave(&dfd_list_lock, flags);
nr_objs = CIRC_CNT(dfd_node_list.head, dfd_node_list.tail,
KFREE_CIRC_BUF_SIZE);
spin_unlock_irqrestore(&dfd_list_lock, flags);
if (nr_objs == 0) {
pr_info("nothing more to reclaim from here!\n");
nr_objs = -1;
}
out:
return nr_objs;
}
static struct shrinker dfd_shrinker = {
.shrink = dfd_shrink,
.seeks = DEFAULT_SEEKS
};
static int __init dfd_shrinker_init(void)
{
register_shrinker(&dfd_shrinker);
return 0;
}
static void __exit dfd_shrinker_exit(void)
{
unregister_shrinker(&dfd_shrinker);
}
module_init(dfd_shrinker_init);
module_exit(dfd_shrinker_exit);
#endif
static inline int dfd_check_magic_any(void *addr)
{
return (((unsigned int *)addr)[0] == KFREE_FREE_MAGIC ||
((unsigned int *)addr)[1] == KFREE_FREE_MAGIC ||
((unsigned int *)addr)[2] == KFREE_FREE_MAGIC ||
((unsigned int *)addr)[3] == KFREE_FREE_MAGIC);
}
static inline int dfd_check_magic_all(void *addr)
{
return (((unsigned int *)addr)[0] == KFREE_FREE_MAGIC &&
((unsigned int *)addr)[1] == KFREE_FREE_MAGIC &&
((unsigned int *)addr)[2] == KFREE_FREE_MAGIC &&
((unsigned int *)addr)[3] == KFREE_FREE_MAGIC);
}
static inline void dfd_set_magic(void *addr)
{
BUILD_BUG_ON(KMALLOC_MIN_SIZE < 16);
((unsigned long *)addr)[0] = KFREE_FREE_MAGIC;
((unsigned long *)addr)[1] = KFREE_FREE_MAGIC;
((unsigned long *)addr)[2] = KFREE_FREE_MAGIC;
((unsigned long *)addr)[3] = KFREE_FREE_MAGIC;
}
static inline void dfd_clear_magic(void *addr)
{
BUILD_BUG_ON(KMALLOC_MIN_SIZE < 16);
((unsigned long *)addr)[0] = 0;
((unsigned long *)addr)[1] = 0;
((unsigned long *)addr)[2] = 0;
((unsigned long *)addr)[3] = 0;
}
static void __hexdump(void *mem, unsigned long size)
{
#define WORDS_PER_LINE 4
#define WORD_SIZE 4
#define LINE_SIZE (WORDS_PER_LINE * WORD_SIZE)
#define LINE_BUF_SIZE (WORDS_PER_LINE * WORD_SIZE * 3 \
+ WORDS_PER_LINE + 4)
unsigned long addr;
char linebuf[LINE_BUF_SIZE];
int numline = size / LINE_SIZE;
int i;
for (i = 0; i < numline; i++) {
addr = (unsigned long)mem + i * LINE_SIZE;
hex_dump_to_buffer((const void *)addr,
LINE_SIZE, LINE_SIZE,
WORD_SIZE, linebuf, sizeof(linebuf), 1);
pr_info(" %lx : %s\n", addr, linebuf);
}
}
void *kfree_hook(void *p, void *caller)
{
unsigned long flags;
struct dfd_node *match = NULL;
void *tofree = NULL;
unsigned long addr = (unsigned long)p;
struct dfd_node entry;
struct dfd_node *pentry;
if (!virt_addr_valid(addr)) {
/* there are too many NULL pointers so don't print for NULL */
if (addr)
pr_debug("trying to free an invalid addr %lx" \
"from %pS\n", addr, caller);
return NULL;
}
if (addr & KFREE_HOOK_BYPASS_MASK || dfd_disabled) {
/* return original address to free */
return (void *)(addr&~(KFREE_HOOK_BYPASS_MASK));
}
spin_lock_irqsave(&dfd_list_lock, flags);
if (dfd_node_list.head == 0)
pr_debug("circular buffer head rounded to zero.");
/* We can detect all the double free in the circular buffer time frame
* if we scan the whole circular buffer all the time, but to minimize
* the performance degradation we will just check for the magic values
* (the number of magic values can be up to KMALLOC_MIN_SIZE/4) */
if (dfd_check_magic_any(p)) {
/* memory that is to be freed may originally have had magic
* value, so search the whole circ buf for an actual match */
match = circ_buf_lookup(&dfd_node_list, p);
if (!match) {
pr_debug("magic set but not in circ buf\n");
}
}
if (match) {
pr_err("0x%08lx was already freed by %pS()\n",
(unsigned long)p, match->caller);
spin_unlock_irqrestore(&dfd_list_lock, flags);
if (dfd_panic)
panic("double free detected!");
/* if we don't panic we just return without adding this entry
* to the circular buffer. This means that this kfree is ommited
* and we are just forgiving the double free */
dump_stack();
return NULL;
}
/* mark free magic on the freeing node */
dfd_set_magic(p);
/* do an actual kfree for the oldest entry
* if the circular buffer is full */
if (CIRC_SPACE(dfd_node_list.head, dfd_node_list.tail,
KFREE_CIRC_BUF_SIZE) == 0) {
pentry = circ_buf_get(&dfd_node_list);
if (pentry)
tofree = pentry->addr;
}
/* add the new entry to the circular buffer */
entry.addr = p;
entry.caller = caller;
circ_buf_put(&dfd_node_list, &entry);
if (tofree) {
if (unlikely(!dfd_check_magic_all(tofree))) {
pr_emerg("Use after free detected on the node " \
"0x%lx which was freed by %pS.\n", \
(unsigned long)tofree,
pentry->caller);
__hexdump((void *)tofree, KMALLOC_MIN_SIZE);
pr_err("\n");
}
dfd_clear_magic(tofree);
spin_unlock_irqrestore(&dfd_list_lock, flags);
/* do the real kfree */
kfree((void *)((unsigned long)tofree | KFREE_HOOK_BYPASS_MASK));
return NULL;
}
spin_unlock_irqrestore(&dfd_list_lock, flags);
return NULL;
}

1349
drivers/debug/sec_debug.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,363 @@
/*
* drivers/debug/sec_debug_force_err.c
*
* COPYRIGHT(C) 2017 Samsung Electronics Co., Ltd. All Right Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/preempt.h>
#include <linux/delay.h>
#include <linux/string.h>
#include <linux/smp.h>
#include <linux/io.h>
#include <linux/gfp.h>
#include <linux/cpumask.h>
#include <linux/cpu.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <soc/qcom/scm.h>
#include <linux/sec_debug.h>
#include <linux/sec_debug_user_reset.h>
/* timeout for dog bark/bite */
#define DELAY_TIME 20000
static int force_error(const char *val, const struct kernel_param *kp);
module_param_call(force_error, force_error, NULL, NULL,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
static void __simulate_apps_wdog_bark(void)
{
pr_emerg("Simulating apps watch dog bark\n");
preempt_disable();
mdelay(DELAY_TIME);
preempt_enable();
/* if we reach here, simulation failed */
pr_emerg("Simulation of apps watch dog bark failed\n");
}
static void __simulate_apps_wdog_bite(void)
{
#ifdef CONFIG_HOTPLUG_CPU
int cpu;
for_each_online_cpu(cpu) {
if (0 == cpu)
continue;
cpu_down(cpu);
}
#endif
pr_emerg("Simulating apps watch dog bite\n");
local_irq_disable();
mdelay(DELAY_TIME);
local_irq_enable();
/* if we reach here, simulation had failed */
pr_emerg("Simualtion of apps watch dog bite failed\n");
}
#ifdef CONFIG_SEC_DEBUG_SEC_WDOG_BITE
static void __simulate_secure_wdog_bite(void)
{
#define SCM_SVC_SEC_WDOG_TRIG 0x8
struct scm_desc desc = {
.args[0] = 0,
.arginfo = SCM_ARGS(1),
};
pr_emerg("simulating secure watch dog bite\n");
if (!is_scm_armv8())
scm_call_atomic2(SCM_SVC_BOOT,
SCM_SVC_SEC_WDOG_TRIG, 0, 0);
else
scm_call2(SCM_SIP_FNID(SCM_SVC_BOOT,
SCM_SVC_SEC_WDOG_TRIG), &desc);
/* if we hit, scm_call has failed */
pr_emerg("simulation of secure watch dog bite failed\n");
}
#endif
#ifdef CONFIG_USER_RESET_DEBUG_TEST
extern void qpnp_pon_pmic_wd_trigger(void);
static void __simulate_pmic_wdog_bite(void)
{
pr_emerg("simulating pmic watch dog bite\n");
qpnp_pon_pmic_wd_trigger();
while(1);
}
#endif
#if defined(CONFIG_ARCH_MSM8226) || defined(CONFIG_ARCH_MSM8974)
/*
* Misc data structures needed for simulating bus timeout in
* camera
*/
#define HANG_ADDRESS 0xfda10000
struct clk_pair {
const char *dev;
const char *clk;
};
static struct clk_pair bus_timeout_camera_clocks_on[] = {
/*
* gcc_mmss_noc_cfg_ahb_clk should be on but right
* now this clock is on by default and not accessable.
* Update this table if gcc_mmss_noc_cfg_ahb_clk is
* ever not enabled by default!
*/
{
.dev = "fda0c000.qcom,cci",
.clk = "camss_top_ahb_clk",
},
{
.dev = "fda10000.qcom,vfe",
.clk = "iface_clk",
},
};
static struct clk_pair bus_timeout_camera_clocks_off[] = {
{
.dev = "fda10000.qcom,vfe",
.clk = "camss_vfe_vfe_clk",
}
};
static void bus_timeout_clk_access(struct clk_pair bus_timeout_clocks_off[],
struct clk_pair bus_timeout_clocks_on[],
int off_size, int on_size)
{
size_t i;
/*
* Yes, none of this cleans up properly but the goal here
* is to trigger a hang which is going to kill the rest of
* the system anyway
*/
for (i = 0; i < on_size; i++) {
struct clk *this_clock;
this_clock = clk_get_sys(bus_timeout_clocks_on[i].dev,
bus_timeout_clocks_on[i].clk);
if (!IS_ERR(this_clock))
if (clk_prepare_enable(this_clock))
pr_warn("Device %s: Clock %s not enabled",
bus_timeout_clocks_on[i].clk,
bus_timeout_clocks_on[i].dev);
}
for (i = 0; i < off_size; i++) {
struct clk *this_clock;
this_clock = clk_get_sys(bus_timeout_clocks_off[i].dev,
bus_timeout_clocks_off[i].clk);
if (!IS_ERR(this_clock))
clk_disable_unprepare(this_clock);
}
}
static void simulate_bus_hang(void)
{
/* This simulates bus timeout on camera */
int ret;
uint32_t dummy_value;
uint32_t address = HANG_ADDRESS;
void *hang_address;
struct regulator *r;
/* simulate */
hang_address = ioremap(address, SZ_4K);
r = regulator_get(NULL, "gdsc_vfe");
ret = IS_ERR(r);
if (!ret)
regulator_enable(r);
else
pr_emerg("Unable to get regulator reference\n");
bus_timeout_clk_access(bus_timeout_camera_clocks_off,
bus_timeout_camera_clocks_on,
ARRAY_SIZE(bus_timeout_camera_clocks_off),
ARRAY_SIZE(bus_timeout_camera_clocks_on));
dummy_value = readl_relaxed(hang_address);
mdelay(DELAY_TIME);
/* if we hit here, test had failed */
pr_emerg("Bus timeout test failed...0x%x\n", dummy_value);
iounmap(hang_address);
}
#else /* defined(CONFIG_ARCH_MSM8226) || defined(CONFIG_ARCH_MSM8974) */
static void simulate_bus_hang(void)
{
void __iomem *p;
pr_emerg("Generating Bus Hang!\n");
p = ioremap_nocache(0xFC4B8000, 32);
*(unsigned int *)p = *(unsigned int *)p;
mb(); /* memory barriar to generate bus hang */
pr_info("*p = %x\n", *(unsigned int *)p);
pr_emerg("Clk may be enabled.Try again if it reaches here!\n");
}
#endif /* defined(CONFIG_ARCH_MSM8226) || defined(CONFIG_ARCH_MSM8974) */
static void __simulate_dabort(void)
{
char *buf = NULL;
*buf = 0x0;
}
static void __simulate_pabort(void)
{
((void (*)(void))NULL)();
}
static void __simulate_undef(void)
{
BUG();
}
static void __simulate_dblfree(void)
{
unsigned int *ptr = kmalloc(sizeof(unsigned int), GFP_KERNEL);
kfree(ptr);
msleep(1000);
kfree(ptr);
}
static void __simulate_danglingref(void)
{
unsigned int *ptr = kmalloc(sizeof(unsigned int), GFP_KERNEL);
kfree(ptr);
*ptr = 0x1234;
}
static void __simulate_lowmem(void)
{
size_t i;
for (i = 0; kmalloc(128 * 1024, GFP_KERNEL); i++)
;
pr_emerg("Allocated %zu KB!\n", i * 128);
}
static void __simulate_memcorrupt(void)
{
unsigned int *ptr = kmalloc(sizeof(unsigned int), GFP_KERNEL);
*ptr++ = 4;
*ptr = 2;
panic("MEMORY CORRUPTION");
}
#ifdef CONFIG_FREE_PAGES_RDONLY
static void __check_page_read(void)
{
struct page *page = alloc_pages(GFP_ATOMIC, 0);
unsigned int *ptr = (unsigned int *)page_address(page);
pr_emerg("Test with RD page config.");
__free_pages(page, 0);
*ptr = 0x12345678;
}
#endif
static int force_error(const char *val, const struct kernel_param *kp)
{
size_t i;
struct __magic {
const char *val;
const char *msg;
void (*func)(void);
} magic[] = {
{ "apppdogbark",
"Generating an apps wdog bark!",
&__simulate_apps_wdog_bark },
{ "appdogbite",
"Generating an apps wdog bite!",
&__simulate_apps_wdog_bite },
{ "dabort",
"Generating a data abort exception!",
&__simulate_dabort },
{ "pabort",
"Generating a data abort exception!",
&__simulate_pabort },
{ "undef",
"Generating a undefined instruction exception!",
&__simulate_undef },
{ "bushang",
"Generating a Bus Hang!",
&simulate_bus_hang },
{ "dblfree",
NULL,
&__simulate_dblfree },
{ "danglingref",
NULL,
&__simulate_danglingref },
{ "lowmem",
"Allocating memory until failure!",
&__simulate_lowmem },
{ "memcorrupt",
NULL,
&__simulate_memcorrupt },
#ifdef CONFIG_SEC_DEBUG_SEC_WDOG_BITE
{ "secdogbite",
NULL,
&__simulate_secure_wdog_bite },
#endif
#ifdef CONFIG_USER_RESET_DEBUG_TEST
{ "KP",
"Generating a data abort exception!",
&__simulate_dabort },
{ "DP",
NULL,
&force_watchdog_bark },
{ "WP",
NULL,
&__simulate_secure_wdog_bite },
{ "PMWD",
NULL,
&__simulate_pmic_wdog_bite },
#endif
#ifdef CONFIG_FREE_PAGES_RDONLY
{ "pageRDcheck",
NULL,
&__check_page_read },
#endif
};
pr_emerg("!!!WARN forced error : %s\n", val);
for (i = 0; i < ARRAY_SIZE(magic); i++) {
size_t len = strlen(magic[i].val);
if (strncmp(val, magic[i].val, len))
continue;
if (NULL != magic[i].msg)
pr_emerg("%s\n", magic[i].msg);
magic[i].func();
return 0;
}
pr_emerg("No such error defined for now!\n");
return 0;
}

View File

@@ -0,0 +1,52 @@
/*
* drivers/debug/sec_debug_internal.h
*
* COPYRIGHT(C) 2017-2018 Samsung Electronics Co., Ltd. All Right Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only 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.
*/
#ifndef __SEC_DEBUG_INTERNAL_H__
#define __SEC_DEBUG_INTERNAL_H__
/* [[BEGIN>> sec_debug.c */
extern void dump_all_task_info(void);
extern void dump_cpu_stat(void);
/* <<END]] sec_debug.c */
/* [[BEGIN>> sec_debug_sched_log.c */
extern struct sec_debug_log *secdbg_log;
extern phys_addr_t secdbg_paddr;
extern size_t secdbg_size;
/* <<END]] sec_debug_sched_log.c */
/* out-of-sec_debug */
/* drivers/power/reset/msm-poweroff.c */
extern void set_dload_mode(int on);
/* kernel/init/version.c */
#include <linux/utsname.h>
extern struct uts_namespace init_uts_ns;
/* drivers/base/core.c */
extern struct kset *devices_kset;
/* kernel/printk/printk.c */
extern unsigned int get_sec_log_idx(void);
/* fake pr_xxx macros to prevent checkpatch fails */
#define __printx printk
#define __pr_info(fmt, ...) \
__printx(KERN_INFO fmt, ##__VA_ARGS__)
#define __pr_err(fmt, ...) \
__printx(KERN_ERR fmt, ##__VA_ARGS__)
#endif /* __SEC_DEBUG_INTERNAL_H__ */

View File

@@ -0,0 +1,534 @@
/*
* drivers/debug/sec_debug_partition.c
*
* COPYRIGHT(C) 2006-2017 Samsung Electronics Co., Ltd. All Right Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
#include <linux/module.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/file.h>
#include <linux/syscalls.h>
#include <linux/delay.h>
#include <linux/sec_bsp.h>
#include <linux/sec_debug.h>
#include <linux/sec_debug_summary.h>
#include <linux/sec_debug_user_reset.h>
#include <linux/sec_debug_partition.h>
#define PRINT_MSG_CYCLE 20
/* single global instance */
struct debug_partition_data_s sched_debug_data;
static int in_panic;
static int driver_initialized;
static int ap_health_initialized;
static struct delayed_work dbg_partition_notify_work;
static struct workqueue_struct *dbg_part_wq;
static struct delayed_work ap_health_work;
static ap_health_t ap_health_data;
static DEFINE_MUTEX(ap_health_work_lock);
static DEFINE_MUTEX(debug_partition_mutex);
static BLOCKING_NOTIFIER_HEAD(dbg_partition_notifier_list);
static void debug_partition_operation(struct work_struct *work)
{
int ret;
struct file *filp;
mm_segment_t fs;
struct debug_partition_data_s *sched_data =
container_of(work, struct debug_partition_data_s,
debug_partition_work);
int flag = (sched_data->direction == PARTITION_WR) ?
(O_RDWR | O_SYNC) : O_RDONLY;
static unsigned int err_cnt;
if (!sched_data->value) {
pr_err("%p %x %d %d - value is NULL!!\n",
sched_data->value, sched_data->offset,
sched_data->size, sched_data->direction);
sched_data->error = -ENODATA;
goto sched_data_err;
}
fs = get_fs();
set_fs(get_ds());
sched_data->error = 0;
filp = filp_open(DEBUG_PARTITION_NAME, flag, 0);
if (IS_ERR(filp)) {
if (!(++err_cnt % PRINT_MSG_CYCLE))
pr_err("filp_open failed: %ld[%u]\n",
PTR_ERR(filp), err_cnt);
sched_data->error = PTR_ERR(filp);
goto filp_err;
}
ret = vfs_llseek(filp, sched_data->offset, SEEK_SET);
if (ret < 0) {
pr_err("FAIL LLSEEK\n");
sched_data->error = ret;
goto llseek_err;
}
if (sched_data->direction == PARTITION_RD)
vfs_read(filp, (char __user *)sched_data->value,
sched_data->size, &filp->f_pos);
else if (sched_data->direction == PARTITION_WR)
vfs_write(filp, (char __user *)sched_data->value,
sched_data->size, &filp->f_pos);
llseek_err:
filp_close(filp, NULL);
filp_err:
set_fs(fs);
sched_data_err:
complete(&sched_data->work);
}
static void ap_health_work_write_fn(struct work_struct *work)
{
int ret;
struct file *filp;
mm_segment_t fs;
unsigned long delay = 5 * HZ;
static unsigned int err_cnt;
pr_info("start.\n");
if (!mutex_trylock(&ap_health_work_lock)) {
pr_err("already locked.\n");
delay = 2 * HZ;
goto occupied_retry;
}
fs = get_fs();
set_fs(get_ds());
filp = filp_open(DEBUG_PARTITION_NAME, (O_RDWR | O_SYNC), 0);
if (IS_ERR(filp)) {
if (!(++err_cnt % PRINT_MSG_CYCLE))
pr_err("filp_open failed: %ld[%u]\n",
PTR_ERR(filp), err_cnt);
goto openfail_retry;
}
ret = vfs_llseek(filp, SEC_DEBUG_AP_HEALTH_OFFSET, SEEK_SET);
if (ret < 0) {
pr_err("FAIL LLSEEK\n");
ret = false;
goto seekfail_retry;
}
vfs_write(filp, (char __user *)&ap_health_data,
sizeof(ap_health_t), &filp->f_pos);
if (--ap_health_data.header.need_write)
goto remained;
filp_close(filp, NULL);
set_fs(fs);
mutex_unlock(&ap_health_work_lock);
pr_info("end.\n");
return;
remained:
seekfail_retry:
filp_close(filp, NULL);
openfail_retry:
set_fs(fs);
mutex_unlock(&ap_health_work_lock);
occupied_retry:
queue_delayed_work(dbg_part_wq, &ap_health_work, delay);
pr_info("end, will retry, wr(%u).\n",
ap_health_data.header.need_write);
}
static bool init_lcd_debug_data(void)
{
int ret = true, retry = 0;
struct lcd_debug_t lcd_debug;
pr_info("%s start\n", __func__);
memset((void *)&lcd_debug, 0, sizeof(struct lcd_debug_t));
pr_info("%s lcd_debug size[%ld]\n", __func__, sizeof(struct lcd_debug_t));
do {
if (retry++) {
pr_err("%s : will retry...\n", __func__);
msleep(1000);
}
mutex_lock(&debug_partition_mutex);
sched_debug_data.value = &lcd_debug;
sched_debug_data.offset = SEC_DEBUG_LCD_DEBUG_OFFSET;
sched_debug_data.size = sizeof(struct lcd_debug_t);
sched_debug_data.direction = PARTITION_WR;
schedule_work(&sched_debug_data.debug_partition_work);
wait_for_completion(&sched_debug_data.work);
mutex_unlock(&debug_partition_mutex);
} while (sched_debug_data.error);
pr_info("%s end\n",__func__);
return ret;
}
static void init_ap_health_data(void)
{
pr_info("start\n");
memset((void *)&ap_health_data, 0, sizeof(ap_health_t));
ap_health_data.header.magic = AP_HEALTH_MAGIC;
ap_health_data.header.version = AP_HEALTH_VER;
ap_health_data.header.size = sizeof(ap_health_t);
ap_health_data.spare_magic1 = AP_HEALTH_MAGIC;
ap_health_data.spare_magic2 = AP_HEALTH_MAGIC;
ap_health_data.spare_magic3 = AP_HEALTH_MAGIC;
while (1) {
mutex_lock(&debug_partition_mutex);
sched_debug_data.value = &ap_health_data;
sched_debug_data.offset = SEC_DEBUG_AP_HEALTH_OFFSET;
sched_debug_data.size = sizeof(ap_health_t);
sched_debug_data.direction = PARTITION_WR;
schedule_work(&sched_debug_data.debug_partition_work);
wait_for_completion(&sched_debug_data.work);
mutex_unlock(&debug_partition_mutex);
if (!sched_debug_data.error)
break;
msleep(1000);
}
pr_info("end\n");
}
static void init_debug_partition(void)
{
struct debug_reset_header init_reset_header;
pr_info("start\n");
/*++ add here need init data ++*/
init_ap_health_data();
/*-- add here need init data --*/
while (1) {
mutex_lock(&debug_partition_mutex);
memset(&init_reset_header, 0,
sizeof(struct debug_reset_header));
init_reset_header.magic = DEBUG_PARTITION_MAGIC;
sched_debug_data.value = &init_reset_header;
sched_debug_data.offset = SEC_DEBUG_RESET_HEADER_OFFSET;
sched_debug_data.size = sizeof(struct debug_reset_header);
sched_debug_data.direction = PARTITION_WR;
schedule_work(&sched_debug_data.debug_partition_work);
wait_for_completion(&sched_debug_data.work);
mutex_unlock(&debug_partition_mutex);
if (!sched_debug_data.error)
break;
msleep(1000);
}
pr_info("end\n");
}
static void check_magic_data(void)
{
static int checked_magic;
struct debug_reset_header partition_header = {0,};
if (checked_magic)
return;
pr_info("start\n");
while (1) {
mutex_lock(&debug_partition_mutex);
sched_debug_data.value = &partition_header;
sched_debug_data.offset = SEC_DEBUG_RESET_HEADER_OFFSET;
sched_debug_data.size = sizeof(struct debug_reset_header);
sched_debug_data.direction = PARTITION_RD;
schedule_work(&sched_debug_data.debug_partition_work);
wait_for_completion(&sched_debug_data.work);
mutex_unlock(&debug_partition_mutex);
if (!sched_debug_data.error)
break;
msleep(1000);
}
if (partition_header.magic != DEBUG_PARTITION_MAGIC)
init_debug_partition();
checked_magic = 1;
pr_info("end\n");
}
#define READ_DEBUG_PARTITION(_value, _offset, _size) \
{ \
mutex_lock(&debug_partition_mutex); \
sched_debug_data.value = _value; \
sched_debug_data.offset = _offset; \
sched_debug_data.size = _size; \
sched_debug_data.direction = PARTITION_RD; \
schedule_work(&sched_debug_data.debug_partition_work); \
wait_for_completion(&sched_debug_data.work); \
mutex_unlock(&debug_partition_mutex); \
}
bool read_debug_partition(enum debug_partition_index index, void *value)
{
check_magic_data();
switch (index) {
case debug_index_reset_ex_info:
READ_DEBUG_PARTITION(value,
SEC_DEBUG_EXTRA_INFO_OFFSET,
SEC_DEBUG_EX_INFO_SIZE);
break;
case debug_index_reset_klog_info:
case debug_index_reset_summary_info:
READ_DEBUG_PARTITION(value,
SEC_DEBUG_RESET_HEADER_OFFSET,
sizeof(struct debug_reset_header));
break;
case debug_index_reset_summary:
READ_DEBUG_PARTITION(value,
SEC_DEBUG_RESET_SUMMARY_OFFSET,
SEC_DEBUG_RESET_SUMMARY_SIZE);
break;
case debug_index_reset_klog:
READ_DEBUG_PARTITION(value,
SEC_DEBUG_RESET_KLOG_OFFSET,
SEC_DEBUG_RESET_KLOG_SIZE);
break;
case debug_index_reset_tzlog:
READ_DEBUG_PARTITION(value,
SEC_DEBUG_RESET_TZLOG_OFFSET,
SEC_DEBUG_RESET_TZLOG_SIZE);
break;
case debug_index_ap_health:
READ_DEBUG_PARTITION(value,
SEC_DEBUG_AP_HEALTH_OFFSET,
SEC_DEBUG_AP_HEALTH_SIZE);
break;
case debug_index_reset_extrc_info:
READ_DEBUG_PARTITION(value,
SEC_DEBUG_RESET_EXTRC_OFFSET,
SEC_DEBUG_RESET_EXTRC_SIZE);
break;
case debug_index_lcd_debug_info:
READ_DEBUG_PARTITION(value,
SEC_DEBUG_LCD_DEBUG_OFFSET,
sizeof(struct lcd_debug_t));
break;
case debug_index_modem_info:
READ_DEBUG_PARTITION(value,
SEC_DEBUG_RESET_MODEM_OFFSET,
sizeof(struct sec_debug_summary_data_modem));
break;
default:
return false;
}
return true;
}
EXPORT_SYMBOL(read_debug_partition);
bool write_debug_partition(enum debug_partition_index index, void *value)
{
check_magic_data();
switch (index) {
case debug_index_reset_klog_info:
case debug_index_reset_summary_info:
mutex_lock(&debug_partition_mutex);
sched_debug_data.value =
(struct debug_reset_header *)value;
sched_debug_data.offset = SEC_DEBUG_RESET_HEADER_OFFSET;
sched_debug_data.size =
sizeof(struct debug_reset_header);
sched_debug_data.direction = PARTITION_WR;
schedule_work(&sched_debug_data.debug_partition_work);
wait_for_completion(&sched_debug_data.work);
mutex_unlock(&debug_partition_mutex);
break;
case debug_index_reset_ex_info:
case debug_index_reset_summary:
// do nothing.
break;
case debug_index_lcd_debug_info:
mutex_lock(&debug_partition_mutex);
sched_debug_data.value = (struct lcd_debug_t *)value;
sched_debug_data.offset = SEC_DEBUG_LCD_DEBUG_OFFSET;
sched_debug_data.size = sizeof(struct lcd_debug_t);
sched_debug_data.direction = PARTITION_WR;
schedule_work(&sched_debug_data.debug_partition_work);
wait_for_completion(&sched_debug_data.work);
mutex_unlock(&debug_partition_mutex);
break;
default:
return false;
}
return true;
}
EXPORT_SYMBOL(write_debug_partition);
ap_health_t *ap_health_data_read(void)
{
if (!driver_initialized)
return NULL;
if (ap_health_initialized)
goto out;
if (in_interrupt()) {
pr_info("skip read opt.\n");
return NULL;
}
read_debug_partition(debug_index_ap_health, (void *)&ap_health_data);
if (ap_health_data.header.magic != AP_HEALTH_MAGIC ||
ap_health_data.header.version != AP_HEALTH_VER ||
ap_health_data.header.size != sizeof(ap_health_t) ||
ap_health_data.spare_magic1 != AP_HEALTH_MAGIC ||
ap_health_data.spare_magic2 != AP_HEALTH_MAGIC ||
ap_health_data.spare_magic3 != AP_HEALTH_MAGIC ||
is_boot_recovery()) {
init_ap_health_data();
init_lcd_debug_data();
}
ap_health_initialized = 1;
out:
return &ap_health_data;
}
EXPORT_SYMBOL(ap_health_data_read);
int ap_health_data_write(ap_health_t *data)
{
if (!driver_initialized || !data || !ap_health_initialized)
return -ENODATA;
data->header.need_write++;
if (!in_panic)
queue_delayed_work(dbg_part_wq, &ap_health_work, 0);
return 0;
}
EXPORT_SYMBOL(ap_health_data_write);
int dbg_partition_notifier_register(struct notifier_block *nb)
{
return blocking_notifier_chain_register(
&dbg_partition_notifier_list, nb);
}
EXPORT_SYMBOL(dbg_partition_notifier_register);
static void debug_partition_do_notify(struct work_struct *work)
{
blocking_notifier_call_chain(&dbg_partition_notifier_list,
DBG_PART_DRV_INIT_DONE, NULL);
}
static int dbg_partition_panic_prepare(struct notifier_block *nb,
unsigned long event, void *data)
{
in_panic = 1;
return NOTIFY_DONE;
}
static struct notifier_block dbg_partition_panic_notifier_block = {
.notifier_call = dbg_partition_panic_prepare,
};
static int __init sec_debug_partition_init(void)
{
pr_info("start\n");
sched_debug_data.offset = 0;
sched_debug_data.direction = 0;
sched_debug_data.size = 0;
sched_debug_data.value = NULL;
init_completion(&sched_debug_data.work);
INIT_WORK(&sched_debug_data.debug_partition_work,
debug_partition_operation);
INIT_DELAYED_WORK(&dbg_partition_notify_work,
debug_partition_do_notify);
INIT_DELAYED_WORK(&ap_health_work, ap_health_work_write_fn);
dbg_part_wq = create_singlethread_workqueue("glink_lbsrv");
if (!dbg_part_wq) {
pr_err("fail to create dbg_part_wq!\n");
return -EFAULT;
}
atomic_notifier_chain_register(&panic_notifier_list,
&dbg_partition_panic_notifier_block);
driver_initialized = DRV_INITIALIZED;
schedule_delayed_work(&dbg_partition_notify_work, 2 * HZ);
pr_info("end\n");
return 0;
}
static void __exit sec_debug_partition_exit(void)
{
driver_initialized = DRV_UNINITIALIZED;
cancel_work_sync(&sched_debug_data.debug_partition_work);
cancel_delayed_work_sync(&dbg_partition_notify_work);
pr_info("exit\n");
}
module_init(sec_debug_partition_init);
module_exit(sec_debug_partition_exit);

View File

@@ -0,0 +1,461 @@
/*
* drivers/debug/sec_debug_sched_log.c
*
* COPYRIGHT(C) 2017-2018 Samsung Electronics Co., Ltd. All Right Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only 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.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/sec_debug.h>
#include "sec_debug_internal.h"
/* TODO: set 'NO_ATOMIC_IDX' only for internal testing purpose */
#ifdef NO_ATOMIC_IDX
#define sec_debug_idx_inc_return(__idx) (++(*(__idx)))
#define sec_debug_idx_set(__idx, __v) { (*(__idx)) = __v; }
#else
#define sec_debug_idx_inc_return(__idx) atomic_inc_return((__idx))
#define sec_debug_idx_set(__idx, __v) atomic_sec((__idx), (__v))
#endif
struct sec_debug_log *secdbg_log;
phys_addr_t secdbg_paddr;
size_t secdbg_size;
static int __init sec_dbg_setup(char *str)
{
size_t size = (size_t)memparse(str, &str);
pr_info("str=%s\n", str);
if (size /*&& (size == roundup_pow_of_two(size))*/ && (*str == '@')) {
secdbg_paddr = (phys_addr_t)memparse(++str, NULL);
secdbg_size = size;
}
pr_info("secdbg_paddr = 0x%llx\n", (unsigned long long)secdbg_paddr);
pr_info("secdbg_size = 0x%zx\n", secdbg_size);
return 0;
}
__setup("sec_dbg=", sec_dbg_setup);
/* save last_pet and last_ns with these nice functions */
void sec_debug_save_last_pet(unsigned long long last_pet)
{
if (likely(secdbg_log))
secdbg_log->last_pet = last_pet;
}
EXPORT_SYMBOL(sec_debug_save_last_pet);
void sec_debug_save_last_ns(unsigned long long last_ns)
{
if (likely(secdbg_log))
atomic64_set(&(secdbg_log->last_ns), last_ns);
}
EXPORT_SYMBOL(sec_debug_save_last_ns);
static inline long get_switch_state(bool preempt, struct task_struct *p)
{
return preempt ? TASK_RUNNING | TASK_STATE_MAX : p->state;
}
static __always_inline void __sec_debug_task_sched_log(int cpu, bool preempt,
struct task_struct *task, struct task_struct *prev,
char *msg)
{
struct sched_log *sched_log;
int i;
if (unlikely(!secdbg_log))
return;
if (unlikely(!task && !msg))
return;
i = sec_debug_idx_inc_return(&(secdbg_log->idx_sched[cpu]))
& (SCHED_LOG_MAX - 1);
sched_log = &secdbg_log->sched[cpu][i];
sched_log->time = cpu_clock(cpu);
if (task) {
strlcpy(sched_log->comm, task->comm, sizeof(sched_log->comm));
sched_log->pid = task->pid;
sched_log->pTask = task;
sched_log->prio = task->prio;
strlcpy(sched_log->prev_comm, prev->comm,
sizeof(sched_log->prev_comm));
sched_log->prev_pid = prev->pid;
sched_log->prev_state = get_switch_state(preempt, prev);
sched_log->prev_prio = prev->prio;
} else {
strlcpy(sched_log->comm, msg, sizeof(sched_log->comm));
sched_log->pid = current->pid;
sched_log->pTask = NULL;
}
}
void sec_debug_irq_enterexit_log(unsigned int irq, u64 start_time)
{
struct irq_exit_log *irq_exit_log;
int cpu = smp_processor_id();
int i;
if (unlikely(!secdbg_log))
return;
i = sec_debug_idx_inc_return(&(secdbg_log->idx_irq_exit[cpu]))
& (SCHED_LOG_MAX - 1);
irq_exit_log = &secdbg_log->irq_exit[cpu][i];
irq_exit_log->time = start_time;
irq_exit_log->end_time = cpu_clock(cpu);
irq_exit_log->irq = irq;
irq_exit_log->elapsed_time = irq_exit_log->end_time - start_time;
irq_exit_log->pid = current->pid;
}
void sec_debug_task_sched_log_short_msg(char *msg)
{
__sec_debug_task_sched_log(raw_smp_processor_id(),
false, NULL, NULL, msg);
}
void sec_debug_task_sched_log(int cpu, bool preempt,
struct task_struct *task, struct task_struct *prev)
{
__sec_debug_task_sched_log(cpu, false, task, prev, NULL);
}
void sec_debug_timer_log(unsigned int type, int int_lock, void *fn)
{
struct timer_log *timer_log;
int cpu = smp_processor_id();
int i;
if (unlikely(!secdbg_log))
return;
i = sec_debug_idx_inc_return(&(secdbg_log->idx_timer[cpu]))
& (SCHED_LOG_MAX - 1);
timer_log = &secdbg_log->timer_log[cpu][i];
timer_log->time = cpu_clock(cpu);
timer_log->type = type;
timer_log->int_lock = int_lock;
timer_log->fn = (void *)fn;
timer_log->pid = current->pid;
}
void sec_debug_secure_log(u32 svc_id, u32 cmd_id)
{
struct secure_log *secure_log;
static DEFINE_SPINLOCK(secdbg_securelock);
unsigned long flags;
int cpu;
int i;
if (unlikely(!secdbg_log))
return;
spin_lock_irqsave(&secdbg_securelock, flags);
cpu = smp_processor_id();
i = sec_debug_idx_inc_return(&(secdbg_log->idx_secure[cpu]))
& (TZ_LOG_MAX - 1);
secure_log = &secdbg_log->secure[cpu][i];
secure_log->time = cpu_clock(cpu);
secure_log->svc_id = svc_id;
secure_log->cmd_id = cmd_id;
secure_log->pid = current->pid;
spin_unlock_irqrestore(&secdbg_securelock, flags);
}
void sec_debug_irq_sched_log(unsigned int irq, void *fn,
char *name, unsigned int en)
{
struct irq_log *irq_log;
int cpu = smp_processor_id();
int i;
if (unlikely(!secdbg_log))
return;
i = sec_debug_idx_inc_return(&(secdbg_log->idx_irq[cpu]))
& (SCHED_LOG_MAX - 1);
irq_log = &secdbg_log->irq[cpu][i];
irq_log->time = cpu_clock(cpu);
irq_log->irq = irq;
irq_log->fn = (void *)fn;
irq_log->name = name;
irq_log->en = irqs_disabled();
irq_log->preempt_count = preempt_count();
irq_log->context = &cpu;
irq_log->pid = current->pid;
irq_log->entry_exit = en;
}
#ifdef CONFIG_SEC_DEBUG_MSG_LOG
int sec_debug_msg_log(void *caller, const char *fmt, ...)
{
struct secmsg_log *secmsg_log;
int cpu = smp_processor_id();
int r;
int i;
va_list args;
if (unlikely(!secdbg_log))
return 0;
i = sec_debug_idx_inc_return(&(secdbg_log->idx_secmsg[cpu]))
& (MSG_LOG_MAX - 1);
secmsg_log = &secdbg_log->secmsg[cpu][i];
secmsg_log->time = cpu_clock(cpu);
va_start(args, fmt);
r = vsnprintf(secmsg_log->msg, sizeof(secmsg_log->msg), fmt, args);
va_end(args);
secmsg_log->caller0 = __builtin_return_address(0);
secmsg_log->caller1 = caller;
secmsg_log->task = current->comm;
return r;
}
#endif /* CONFIG_SEC_DEBUG_MSG_LOG */
#ifdef CONFIG_SEC_DEBUG_AVC_LOG
int sec_debug_avc_log(const char *fmt, ...)
{
struct secavc_log *secavc_log;
int cpu = smp_processor_id();
int r;
int i;
va_list args;
if (unlikely(!secdbg_log))
return 0;
i = sec_debug_idx_inc_return(&(secdbg_log->idx_secavc[cpu]))
& (AVC_LOG_MAX - 1);
secavc_log = &secdbg_log->secavc[cpu][i];
va_start(args, fmt);
r = vsnprintf(secavc_log->msg, sizeof(secavc_log->msg), fmt, args);
va_end(args);
return r;
}
#endif /* CONFIG_SEC_DEBUG_AVC_LOG */
#ifdef CONFIG_SEC_DEBUG_DCVS_LOG
void sec_debug_dcvs_log(int cpu_no, unsigned int prev_freq,
unsigned int new_freq)
{
struct dcvs_debug *dcvs_debug;
int i;
if (unlikely(!secdbg_log))
return;
i = sec_debug_idx_inc_return(&(secdbg_log->dcvs_log_idx[cpu_no]))
& (DCVS_LOG_MAX - 1);
dcvs_debug = &secdbg_log->dcvs_log[cpu_no][i];
dcvs_debug->cpu_no = cpu_no;
dcvs_debug->prev_freq = prev_freq;
dcvs_debug->new_freq = new_freq;
dcvs_debug->time = cpu_clock(cpu_no);
}
#endif
#ifdef CONFIG_SEC_DEBUG_FUELGAUGE_LOG
void sec_debug_fuelgauge_log(unsigned int voltage, unsigned short soc,
unsigned short charging_status)
{
struct fuelgauge_debug *fuelgauge_debug;
int cpu = smp_processor_id();
int i;
if (unlikely(!secdbg_log))
return;
i = sec_debug_idx_inc_return(&(secdbg_log->fg_log_idx))
& (FG_LOG_MAX - 1);
fuelgauge_debug = &secdbg_log->fg_log[i];
fuelgauge_debug->time = cpu_clock(cpu);
fuelgauge_debug->voltage = voltage;
fuelgauge_debug->soc = soc;
fuelgauge_debug->charging_status = charging_status;
}
#endif
#ifdef CONFIG_SEC_DEBUG_POWER_LOG
void sec_debug_cpu_lpm_log(int cpu, unsigned int index,
bool success, int entry_exit)
{
struct power_log *power_log;
int i;
if (unlikely(!secdbg_log))
return;
i = sec_debug_idx_inc_return(&(secdbg_log->idx_power[cpu]))
& (POWER_LOG_MAX - 1);
power_log = &secdbg_log->pwr_log[cpu][i];
power_log->time = cpu_clock(cpu);
power_log->pid = current->pid;
power_log->type = CPU_POWER_TYPE;
power_log->cpu.index = index;
power_log->cpu.success = success;
power_log->cpu.entry_exit = entry_exit;
}
void sec_debug_cluster_lpm_log(const char *name, int index,
unsigned long sync_cpus, unsigned long child_cpus,
bool from_idle, int entry_exit)
{
struct power_log *power_log;
int i;
int cpu = smp_processor_id();
if (unlikely(!secdbg_log))
return;
i = sec_debug_idx_inc_return(&(secdbg_log->idx_power[cpu]))
& (POWER_LOG_MAX - 1);
power_log = &secdbg_log->pwr_log[cpu][i];
power_log->time = cpu_clock(cpu);
power_log->pid = current->pid;
power_log->type = CLUSTER_POWER_TYPE;
power_log->cluster.name = (char *) name;
power_log->cluster.index = index;
power_log->cluster.sync_cpus = sync_cpus;
power_log->cluster.child_cpus = child_cpus;
power_log->cluster.from_idle = from_idle;
power_log->cluster.entry_exit = entry_exit;
}
void sec_debug_clock_log(const char *name, unsigned int state,
unsigned int cpu_id, int complete)
{
struct power_log *power_log;
int i;
int cpu = cpu_id;
if (unlikely(!secdbg_log))
return;
i = sec_debug_idx_inc_return(&(secdbg_log->idx_power[cpu_id]))
& (POWER_LOG_MAX - 1);
power_log = &secdbg_log->pwr_log[cpu][i];
power_log->time = cpu_clock(cpu_id);
power_log->pid = current->pid;
power_log->type = CLOCK_RATE_TYPE;
power_log->clk_rate.name = (char *)name;
power_log->clk_rate.state = state;
power_log->clk_rate.cpu_id = cpu_id;
power_log->clk_rate.complete = complete;
}
#endif /* CONFIG_SEC_DEBUG_POWER_LOG */
static int __init sec_debug_sched_log_init(void)
{
size_t i;
struct sec_debug_log *vaddr;
size_t size;
if (secdbg_paddr == 0 || secdbg_size == 0) {
pr_info("sec debug buffer not provided. Using kmalloc..\n");
size = sizeof(struct sec_debug_log);
vaddr = kzalloc(size, GFP_KERNEL);
} else {
size = secdbg_size;
vaddr = ioremap_wc(secdbg_paddr, secdbg_size);
}
pr_info("vaddr=0x%p paddr=0x%llx size=0x%zx sizeof(struct sec_debug_log)=0x%zx\n",
vaddr, (uint64_t)secdbg_paddr,
secdbg_size, sizeof(struct sec_debug_log));
if ((!vaddr) || (sizeof(struct sec_debug_log) > size)) {
pr_err("ERROR! init failed!\n");
return -EFAULT;
}
memset_io(vaddr->sched, 0x0, sizeof(vaddr->sched));
memset_io(vaddr->irq, 0x0, sizeof(vaddr->irq));
memset_io(vaddr->irq_exit, 0x0, sizeof(vaddr->irq_exit));
memset_io(vaddr->timer_log, 0x0, sizeof(vaddr->timer_log));
memset_io(vaddr->secure, 0x0, sizeof(vaddr->secure));
#ifdef CONFIG_SEC_DEBUG_MSG_LOG
memset_io(vaddr->secmsg, 0x0, sizeof(vaddr->secmsg));
#endif
#ifdef CONFIG_SEC_DEBUG_AVC_LOG
memset_io(vaddr->secavc, 0x0, sizeof(vaddr->secavc));
#endif
for (i = 0; i < num_possible_cpus(); i++) {
sec_debug_idx_set(&(vaddr->idx_sched[i]), -1);
sec_debug_idx_set(&(vaddr->idx_irq[i]), -1);
sec_debug_idx_set(&(vaddr->idx_secure[i]), -1);
sec_debug_idx_set(&(vaddr->idx_irq_exit[i]), -1);
sec_debug_idx_set(&(vaddr->idx_timer[i]), -1);
#ifdef CONFIG_SEC_DEBUG_MSG_LOG
sec_debug_idx_set(&(vaddr->idx_secmsg[i]), -1);
#endif
#ifdef CONFIG_SEC_DEBUG_AVC_LOG
sec_debug_idx_set(&(vaddr->idx_secavc[i]), -1);
#endif
#ifdef CONFIG_SEC_DEBUG_DCVS_LOG
sec_debug_idx_set(&(vaddr->dcvs_log_idx[i]), -1);
#endif
#ifdef CONFIG_SEC_DEBUG_POWER_LOG
sec_debug_idx_set(&(vaddr->idx_power[i]), -1);
#endif
}
#ifdef CONFIG_SEC_DEBUG_FUELGAUGE_LOG
sec_debug_idx_set(&(vaddr->fg_log_idx), -1);
#endif
secdbg_log = vaddr;
pr_info("init done\n");
return 0;
}
arch_initcall_sync(sec_debug_sched_log_init);

View File

@@ -0,0 +1,629 @@
/*
* drivers/debug/sec_debug_summary.c
*
* driver supporting debug functions for Samsung device
*
* COPYRIGHT(C) 2006-2017 Samsung Electronics Co., Ltd. All Right Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/ctype.h>
#include <soc/qcom/smem.h>
#include <linux/ptrace.h>
#include <asm/processor.h>
#include <asm/irq.h>
#include <asm/memory.h>
#include <linux/sec_bsp.h>
#include <linux/sec_debug.h>
#include <linux/sec_debug_user_reset.h>
#include <linux/sec_debug_summary.h>
#include "sec_debug_internal.h"
struct sec_debug_summary *secdbg_summary;
struct sec_debug_summary_data_apss *secdbg_apss;
static char build_root[] = __FILE__;
static uint32_t tzapps_start_addr;
static uint32_t tzapps_size;
char *sec_debug_arch_desc;
static unsigned long cpu_buf_vaddr;
static unsigned long cpu_buf_paddr;
static unsigned long cpu_data_vaddr;
static unsigned long cpu_data_paddr;
static unsigned long max_cpu_ctx_size;
#ifdef CONFIG_SEC_DEBUG_VERBOSE_SUMMARY_HTML
unsigned int cpu_frequency[CONFIG_NR_CPUS];
unsigned int cpu_volt[CONFIG_NR_CPUS];
char cpu_state[CONFIG_NR_CPUS][VAR_NAME_MAX];
EXPORT_SYMBOL(cpu_frequency);
EXPORT_SYMBOL(cpu_volt);
EXPORT_SYMBOL(cpu_state);
#endif
#ifdef CONFIG_ARM64
#define ARM_PT_REG_PC pc
#define ARM_PT_REG_LR regs[30]
#else
#define ARM_PT_REG_PC ARM_pc
#define ARM_PT_REG_LR ARM_lr
#endif
int sec_debug_save_die_info(const char *str, struct pt_regs *regs)
{
#ifdef CONFIG_USER_RESET_DEBUG
_kern_ex_info_t *p_ex_info;
#endif
if (!secdbg_apss)
return -ENOMEM;
snprintf(secdbg_apss->excp.pc_sym, sizeof(secdbg_apss->excp.pc_sym),
"%pS", (void *)regs->ARM_PT_REG_PC);
snprintf(secdbg_apss->excp.lr_sym, sizeof(secdbg_apss->excp.lr_sym),
"%pS", (void *)regs->ARM_PT_REG_LR);
#ifdef CONFIG_USER_RESET_DEBUG
sec_debug_store_extc_idx(false);
if (sec_debug_reset_ex_info) {
p_ex_info = &sec_debug_reset_ex_info->kern_ex_info.info;
if (p_ex_info->cpu == -1) {
int slen;
char *msg;
p_ex_info->cpu = smp_processor_id();
snprintf(p_ex_info->task_name,
sizeof(p_ex_info->task_name), "%s", current->comm);
p_ex_info->ktime = local_clock();
snprintf(p_ex_info->pc,
sizeof(p_ex_info->pc), "%pS", (void *)regs->ARM_PT_REG_PC);
snprintf(p_ex_info->lr,
sizeof(p_ex_info->lr), "%pS", (void *)regs->ARM_PT_REG_LR);
slen = snprintf(p_ex_info->panic_buf,
sizeof(p_ex_info->panic_buf), "%s", str);
msg = p_ex_info->panic_buf;
if ((slen >= 1) && (msg[slen-1] == '\n'))
msg[slen-1] = 0;
}
}
#endif
return 0;
}
int sec_debug_save_panic_info(const char *str, unsigned long caller)
{
#ifdef CONFIG_USER_RESET_DEBUG
_kern_ex_info_t *p_ex_info;
#endif
if (!secdbg_apss)
return -ENOMEM;
snprintf(secdbg_apss->excp.panic_caller,
sizeof(secdbg_apss->excp.panic_caller), "%pS", (void *)caller);
snprintf(secdbg_apss->excp.panic_msg,
sizeof(secdbg_apss->excp.panic_msg), "%s", str);
snprintf(secdbg_apss->excp.thread,
sizeof(secdbg_apss->excp.thread), "%s:%d", current->comm,
task_pid_nr(current));
#ifdef CONFIG_USER_RESET_DEBUG
sec_debug_store_extc_idx(false);
if (sec_debug_reset_ex_info) {
p_ex_info = &sec_debug_reset_ex_info->kern_ex_info.info;
if (p_ex_info->cpu == -1) {
int slen;
char *msg;
p_ex_info->cpu = smp_processor_id();
snprintf(p_ex_info->task_name,
sizeof(p_ex_info->task_name), "%s", current->comm);
p_ex_info->ktime = local_clock();
snprintf(p_ex_info->pc,
sizeof(p_ex_info->pc), "%pS", (void *)(caller-0x4));
snprintf(p_ex_info->lr,
sizeof(p_ex_info->lr), "%pS", (void *)caller);
slen = snprintf(p_ex_info->panic_buf,
sizeof(p_ex_info->panic_buf), "%s", str);
msg = p_ex_info->panic_buf;
if ((slen >= 1) && (msg[slen-1] == '\n'))
msg[slen-1] = 0;
}
}
#endif
return 0;
}
int sec_debug_summary_add_infomon(char *name, unsigned int size, phys_addr_t pa)
{
if (!secdbg_apss)
return -ENOMEM;
if (secdbg_apss->info_mon.idx >= ARRAY_SIZE(secdbg_apss->info_mon.var))
return -ENOMEM;
strlcpy(secdbg_apss->info_mon.var[secdbg_apss->info_mon.idx].name,
name, sizeof(secdbg_apss->info_mon.var[0].name));
secdbg_apss->info_mon.var[secdbg_apss->info_mon.idx].sizeof_type
= size;
secdbg_apss->info_mon.var[secdbg_apss->info_mon.idx].var_paddr = pa;
secdbg_apss->info_mon.idx++;
return 0;
}
int sec_debug_summary_add_varmon(char *name, unsigned int size, phys_addr_t pa)
{
if (!secdbg_apss)
return -ENOMEM;
if (secdbg_apss->var_mon.idx >= ARRAY_SIZE(secdbg_apss->var_mon.var))
return -ENOMEM;
strlcpy(secdbg_apss->var_mon.var[secdbg_apss->var_mon.idx].name, name,
sizeof(secdbg_apss->var_mon.var[0].name));
secdbg_apss->var_mon.var[secdbg_apss->var_mon.idx].sizeof_type = size;
secdbg_apss->var_mon.var[secdbg_apss->var_mon.idx].var_paddr = pa;
secdbg_apss->var_mon.idx++;
return 0;
}
#ifdef CONFIG_SEC_DEBUG_MDM_FILE_INFO
void sec_set_mdm_summary_info(char *str_buf)
{
snprintf(secdbg_apss->mdmerr_info,
sizeof(secdbg_apss->mdmerr_info), "%s", str_buf);
}
#endif
static int ___build_root_init(char *str)
{
char *st, *ed;
int len;
ed = strstr(str, "/android/kernel");
if (!ed || ed == str)
return -1;
*ed = '\0';
st = strrchr(str, '/');
if (!st)
return -1;
st++;
len = (unsigned long)ed - (unsigned long)st + 1;
memmove(str, st, len);
return 0;
}
#ifdef CONFIG_SEC_DEBUG_VERBOSE_SUMMARY_HTML
void sec_debug_save_cpu_freq_voltage(int cpu, int flag, unsigned long value)
{
if (SAVE_FREQ == flag)
cpu_frequency[cpu] = value;
else if (SAVE_VOLT == flag)
cpu_volt[cpu] = (unsigned int)value;
}
#else
void sec_debug_save_cpu_freq_voltage(int cpu, int flag, unsigned long value)
{
}
#endif
void sec_debug_secure_app_addr_size(uint32_t addr, uint32_t size)
{
tzapps_start_addr = addr;
tzapps_size = size;
}
static int __init _set_kconst(struct sec_debug_summary_data_apss *p)
{
p->kconst.nr_cpus = NR_CPUS;
p->kconst.per_cpu_offset.pa = virt_to_phys(__per_cpu_offset);
p->kconst.per_cpu_offset.size = sizeof(__per_cpu_offset[0]);
p->kconst.per_cpu_offset.count = sizeof(__per_cpu_offset) /
sizeof(__per_cpu_offset[0]);
p->kconst.phys_offset = PHYS_OFFSET;
p->kconst.page_offset = PAGE_OFFSET;
p->kconst.va_bits = VA_BITS;
p->kconst.kimage_vaddr = kimage_vaddr;
p->kconst.kimage_voffset = kimage_voffset;
return 0;
}
static int __init summary_init_infomon(void)
{
if (___build_root_init(build_root) == 0)
ADD_STR_TO_INFOMON(build_root);
ADD_STR_TO_INFOMON(linux_banner);
#ifdef CONFIG_SAMSUNG_PRODUCT_SHIP
sec_debug_summary_add_infomon("Kernel cmdline", -1,
__pa(erased_command_line));
#else
sec_debug_summary_add_infomon("Kernel cmdline", -1,
__pa(saved_command_line));
#endif
sec_debug_summary_add_infomon("Hardware name", -1,
__pa(sec_debug_arch_desc));
return 0;
}
static int __init summary_init_varmon(void)
{
uint64_t last_pet_paddr = 0;
uint64_t last_ns_paddr = 0;
/* save paddrs of last_pet und last_ns */
if (secdbg_paddr && secdbg_log) {
last_pet_paddr = secdbg_paddr +
offsetof(struct sec_debug_log, last_pet);
last_ns_paddr = secdbg_paddr +
offsetof(struct sec_debug_log, last_ns);
sec_debug_summary_add_varmon("last_pet",
sizeof((secdbg_log->last_pet)), last_pet_paddr);
sec_debug_summary_add_varmon("last_ns",
sizeof((secdbg_log->last_ns.counter)),
last_ns_paddr);
} else
pr_emerg("**** secdbg_log or secdbg_paddr is not initialized ****\n");
#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
ADD_VAR_TO_VARMON(boot_reason);
ADD_VAR_TO_VARMON(cold_boot);
#endif
#ifdef CONFIG_SEC_DEBUG_VERBOSE_SUMMARY_HTML
for (i = 0; i < CONFIG_NR_CPUS; i++) {
ADD_STR_ARRAY_TO_VARMON(cpu_state[i], i, CPU_STAT_CORE);
ADD_ARRAY_TO_VARMON(cpu_frequency[i], i, CPU_FREQ_CORE);
ADD_ARRAY_TO_VARMON(cpu_volt[i], i, CPU_VOLT_CORE);
}
#endif
return 0;
}
static int __init summary_init_sched_log(struct sec_debug_summary_data_apss *p)
{
if (!secdbg_paddr)
return -ENOMEM;
p->sched_log.sched_idx_paddr = secdbg_paddr +
offsetof(struct sec_debug_log, idx_sched);
p->sched_log.sched_buf_paddr = secdbg_paddr +
offsetof(struct sec_debug_log, sched);
p->sched_log.sched_struct_sz =
sizeof(struct sched_log);
p->sched_log.sched_array_cnt = SCHED_LOG_MAX;
p->sched_log.irq_idx_paddr = secdbg_paddr +
offsetof(struct sec_debug_log, idx_irq);
p->sched_log.irq_buf_paddr = secdbg_paddr +
offsetof(struct sec_debug_log, irq);
p->sched_log.irq_struct_sz =
sizeof(struct irq_log);
p->sched_log.irq_array_cnt = SCHED_LOG_MAX;
p->sched_log.secure_idx_paddr = secdbg_paddr +
offsetof(struct sec_debug_log, idx_secure);
p->sched_log.secure_buf_paddr = secdbg_paddr +
offsetof(struct sec_debug_log, secure);
p->sched_log.secure_struct_sz =
sizeof(struct secure_log);
p->sched_log.secure_array_cnt = TZ_LOG_MAX;
p->sched_log.irq_exit_idx_paddr = secdbg_paddr +
offsetof(struct sec_debug_log, idx_irq_exit);
p->sched_log.irq_exit_buf_paddr = secdbg_paddr +
offsetof(struct sec_debug_log, irq_exit);
p->sched_log.irq_exit_struct_sz =
sizeof(struct irq_exit_log);
p->sched_log.irq_exit_array_cnt = SCHED_LOG_MAX;
#ifdef CONFIG_SEC_DEBUG_MSG_LOG
p->sched_log.msglog_idx_paddr = secdbg_paddr +
offsetof(struct sec_debug_log, idx_secmsg);
p->sched_log.msglog_buf_paddr = secdbg_paddr +
offsetof(struct sec_debug_log, secmsg);
p->sched_log.msglog_struct_sz =
sizeof(struct secmsg_log);
p->sched_log.msglog_array_cnt = MSG_LOG_MAX;
#else
p->sched_log.msglog_idx_paddr = 0;
p->sched_log.msglog_buf_paddr = 0;
p->sched_log.msglog_struct_sz = 0;
p->sched_log.msglog_array_cnt = 0;
#endif
return 0;
}
static unsigned long get_wdog_regsave_paddr(void)
{
return __pa(&cpu_buf_paddr);
}
unsigned int get_last_pet_paddr(void)
{
#if 0 // MUST BE CHECK
return virt_to_phys(&wdog_dd->last_pet);
#else
return 0;
#endif
}
void sec_debug_summary_bark_dump(unsigned long cpu_data,
unsigned long pcpu_data, unsigned long cpu_buf,
unsigned long pcpu_buf, unsigned long cpu_ctx_size)
{
cpu_data_vaddr = cpu_data;
cpu_data_paddr = pcpu_data;
cpu_buf_vaddr = cpu_buf;
cpu_buf_paddr = pcpu_buf;
max_cpu_ctx_size = cpu_ctx_size;
}
static int sec_debug_summary_set_msm_dump_info(
struct sec_debug_summary_data_apss *apss)
{
apss->cpu_reg.msm_dump_info.cpu_data_paddr = cpu_data_paddr;
apss->cpu_reg.msm_dump_info.cpu_buf_paddr = cpu_buf_paddr;
apss->cpu_reg.msm_dump_info.cpu_ctx_size = max_cpu_ctx_size;
apss->cpu_reg.msm_dump_info.offset = 0x10;
pr_info("cpu_data_paddr = 0x%llx\n",
apss->cpu_reg.msm_dump_info.cpu_data_paddr);
pr_info("cpu_buf_paddr = 0x%llx\n",
apss->cpu_reg.msm_dump_info.cpu_buf_paddr);
return 0;
}
static int __init summary_init_core_reg(struct sec_debug_summary_data_apss *p)
{
/* setup sec debug core reg info */
p->cpu_reg.sec_debug_core_reg_paddr = virt_to_phys(&sec_debug_core_reg);
pr_info("sec_debug_core_reg_paddr = 0x%llx\n",
p->cpu_reg.sec_debug_core_reg_paddr);
#ifdef CONFIG_QCOM_MEMORY_DUMP_V2
/* setup qc core reg info */
sec_debug_summary_set_msm_dump_info(p);
#endif
return 0;
}
static int __init summary_init_avc_log(struct sec_debug_summary_data_apss *p)
{
if (!secdbg_paddr)
return -EINVAL;
#ifdef CONFIG_SEC_DEBUG_AVC_LOG
p->avc_log.secavc_idx_paddr = secdbg_paddr +
offsetof(struct sec_debug_log, idx_secavc);
p->avc_log.secavc_buf_paddr = secdbg_paddr +
offsetof(struct sec_debug_log, secavc);
p->avc_log.secavc_struct_sz =
sizeof(struct secavc_log);
p->avc_log.secavc_array_cnt = AVC_LOG_MAX;
#else
p->avc_log.secavc_idx_paddr = 0;
p->avc_log.secavc_buf_paddr = 0;
p->avc_log.secavc_struct_sz = 0;
p->avc_log.secavc_array_cnt = 0;
#endif
return 0;
}
int sec_debug_is_modem_separate_debug_ssr(void)
{
return secdbg_summary->priv.modem.seperate_debug;
}
#define SET_MEMBER_TYPE_INFO(PTR, TYPE, MEMBER) \
{ \
(PTR)->size = sizeof(((TYPE *)0)->MEMBER); \
(PTR)->offset = offsetof(TYPE, MEMBER); \
}
int summary_set_task_info(struct sec_debug_summary_data_apss *apss)
{
extern struct task_struct init_task;
apss->task.stack_size = THREAD_SIZE;
apss->task.start_sp = THREAD_START_SP;
apss->task.irq_stack.pcpu_stack = (uint64_t)&irq_stack;
apss->task.irq_stack.size = IRQ_STACK_SIZE;
apss->task.irq_stack.start_sp = IRQ_STACK_START_SP;
apss->task.ti.struct_size = sizeof(struct thread_info);
SET_MEMBER_TYPE_INFO(&apss->task.ti.flags, struct thread_info, flags);
SET_MEMBER_TYPE_INFO(&apss->task.ts.cpu, struct task_struct, cpu);
apss->task.ts.struct_size = sizeof(struct task_struct);
SET_MEMBER_TYPE_INFO(&apss->task.ts.state, struct task_struct, state);
SET_MEMBER_TYPE_INFO(&apss->task.ts.exit_state, struct task_struct,
exit_state);
SET_MEMBER_TYPE_INFO(&apss->task.ts.stack, struct task_struct, stack);
SET_MEMBER_TYPE_INFO(&apss->task.ts.flags, struct task_struct, flags);
SET_MEMBER_TYPE_INFO(&apss->task.ts.on_cpu, struct task_struct, on_cpu);
SET_MEMBER_TYPE_INFO(&apss->task.ts.pid, struct task_struct, pid);
SET_MEMBER_TYPE_INFO(&apss->task.ts.comm, struct task_struct, comm);
SET_MEMBER_TYPE_INFO(&apss->task.ts.tasks_next, struct task_struct,
tasks.next);
SET_MEMBER_TYPE_INFO(&apss->task.ts.thread_group_next,
struct task_struct, thread_group.next);
SET_MEMBER_TYPE_INFO(&apss->task.ts.fp, struct task_struct,
thread.cpu_context.fp);
SET_MEMBER_TYPE_INFO(&apss->task.ts.sp, struct task_struct,
thread.cpu_context.sp);
SET_MEMBER_TYPE_INFO(&apss->task.ts.pc, struct task_struct,
thread.cpu_context.pc);
#ifdef CONFIG_SCHED_INFO
/* sched_info */
SET_MEMBER_TYPE_INFO(&apss->task.ts.sched_info__pcount,
struct task_struct, sched_info.pcount);
SET_MEMBER_TYPE_INFO(&apss->task.ts.sched_info__run_delay,
struct task_struct,
sched_info.run_delay);
SET_MEMBER_TYPE_INFO(&apss->task.ts.sched_info__last_arrival,
struct task_struct,
sched_info.last_arrival);
SET_MEMBER_TYPE_INFO(&apss->task.ts.sched_info__last_queued,
struct task_struct,
sched_info.last_queued);
#endif
apss->task.init_task = (uint64_t)&init_task;
apss->task.ropp.magic = 0x0;
return 0;
}
#ifdef CONFIG_MSM_PM
void summary_set_lpm_info_cci(uint64_t paddr)
{
if (secdbg_apss) {
pr_info("%s : 0x%llx\n", __func__, paddr);
secdbg_apss->aplpm.p_cci = paddr;
}
}
#else
void summary_set_lpm_info_cci(uint64_t phy_addr)
{
}
#endif
void * sec_debug_summary_get_modem(void)
{
if (secdbg_summary) {
return (void *)&secdbg_summary->priv.modem;
} else {
pr_info("%s : secdbg_summary is null.\n", __func__);
return NULL;
}
}
int __init sec_debug_summary_init(void)
{
#ifdef CONFIG_SEC_DEBUG_VERBOSE_SUMMARY_HTML
short i;
#endif
pr_info("SMEM_ID_VENDOR2=0x%x size=0x%lx\n",
(unsigned int)SMEM_ID_VENDOR2,
sizeof(struct sec_debug_summary));
/* set summary address in smem for other subsystems to see */
secdbg_summary = (struct sec_debug_summary *)smem_alloc(
SMEM_ID_VENDOR2,
sizeof(struct sec_debug_summary),
0,
SMEM_ANY_HOST_FLAG);
if (secdbg_summary == NULL) {
pr_info("smem alloc failed!\n");
return -ENOMEM;
}
memset(secdbg_summary, 0, (unsigned long)sizeof(secdbg_summary));
secdbg_summary->secure_app_start_addr = tzapps_start_addr;
secdbg_summary->secure_app_size = tzapps_size;
secdbg_apss = &secdbg_summary->priv.apss;
secdbg_summary->apss = (struct sec_debug_summary_data_apss *)
(smem_virt_to_phys(&secdbg_summary->priv.apss)&0xFFFFFFFF);
secdbg_summary->rpm = (struct sec_debug_summary_data *)
(smem_virt_to_phys(&secdbg_summary->priv.rpm)&0xFFFFFFFF);
secdbg_summary->modem = (struct sec_debug_summary_data_modem *)
(smem_virt_to_phys(&secdbg_summary->priv.modem)&0xFFFFFFFF);
secdbg_summary->dsps = (struct sec_debug_summary_data *)
(smem_virt_to_phys(&secdbg_summary->priv.dsps)&0xFFFFFFFF);
pr_info("apss(%lx) rpm(%lx) modem(%lx) dsps(%lx)\n",
(unsigned long)secdbg_summary->apss,
(unsigned long)secdbg_summary->rpm,
(unsigned long)secdbg_summary->modem,
(unsigned long)secdbg_summary->dsps);
strlcpy(secdbg_apss->name, "APSS", sizeof(secdbg_apss->name) + 1);
strlcpy(secdbg_apss->state, "Init", sizeof(secdbg_apss->state) + 1);
secdbg_apss->nr_cpus = CONFIG_NR_CPUS;
sec_debug_summary_set_kloginfo(&secdbg_apss->log.first_idx_paddr,
&secdbg_apss->log.next_idx_paddr,
&secdbg_apss->log.log_paddr, &secdbg_apss->log.size_paddr);
secdbg_apss->tz_core_dump =
(struct msm_dump_data **)get_wdog_regsave_paddr();
summary_init_infomon();
summary_init_varmon();
summary_init_sched_log(secdbg_apss);
summary_init_core_reg(secdbg_apss);
summary_init_avc_log(secdbg_apss);
sec_debug_summary_set_kallsyms_info(secdbg_apss);
_set_kconst(secdbg_apss);
#ifdef CONFIG_QCOM_RTB
sec_debug_summary_set_rtb_info(secdbg_apss);
#endif
summary_set_task_info(secdbg_apss);
summary_set_lpm_info_cluster(secdbg_apss);
summary_set_lpm_info_runqueues(secdbg_apss);
summary_set_msm_memdump_info(secdbg_apss);
/* fill magic nubmer last to ensure data integrity when the magic
* numbers are written
*/
secdbg_summary->magic[0] = SEC_DEBUG_SUMMARY_MAGIC0;
secdbg_summary->magic[1] = SEC_DEBUG_SUMMARY_MAGIC1;
secdbg_summary->magic[2] = SEC_DEBUG_SUMMARY_MAGIC2;
secdbg_summary->magic[3] = SEC_DEBUG_SUMMARY_MAGIC3;
return 0;
}
subsys_initcall_sync(sec_debug_summary_init);

View File

@@ -0,0 +1,923 @@
/*
* drivers/debug/sec_debug_user_reset.c
*
* COPYRIGHT(C) 2006-2018 Samsung Electronics Co., Ltd. All Right Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only 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.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__
#include <asm/stacktrace.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/notifier.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/fcntl.h>
#include <linux/fs.h>
#include <linux/sec_debug.h>
#include <linux/sec_debug_summary.h>
#include <linux/sec_debug_user_reset.h>
#include <linux/sec_debug_partition.h>
#include <linux/sec_param.h>
#include <linux/sec_class.h>
#include "sec_debug_internal.h"
static char rr_str[][3] = {
[USER_UPLOAD_CAUSE_SMPL] = "SP",
[USER_UPLOAD_CAUSE_WTSR] = "WP",
[USER_UPLOAD_CAUSE_WATCHDOG] = "DP",
[USER_UPLOAD_CAUSE_PANIC] = "KP",
[USER_UPLOAD_CAUSE_MANUAL_RESET] = "MP",
[USER_UPLOAD_CAUSE_POWER_RESET] = "PP",
[USER_UPLOAD_CAUSE_REBOOT] = "RP",
[USER_UPLOAD_CAUSE_BOOTLOADER_REBOOT] = "BP",
[USER_UPLOAD_CAUSE_POWER_ON] = "NP",
[USER_UPLOAD_CAUSE_THERMAL] = "TP",
[USER_UPLOAD_CAUSE_UNKNOWN] = "NP",
};
char *klog_buf;
uint32_t klog_size;
char *klog_read_buf;
struct debug_reset_header *klog_info;
static DEFINE_MUTEX(klog_mutex);
char *summary_buf;
struct debug_reset_header *summary_info;
static DEFINE_MUTEX(summary_mutex);
static unsigned reset_reason = 0xFFEEFFEE;
char *tzlog_buf;
struct debug_reset_header *tzlog_info;
static DEFINE_MUTEX(tzlog_mutex);
static int reset_write_cnt = -1;
uint32_t sec_debug_get_reset_reason(void)
{
return reset_reason;
}
EXPORT_SYMBOL(sec_debug_get_reset_reason);
int sec_debug_get_reset_write_cnt(void)
{
return reset_write_cnt;
}
EXPORT_SYMBOL(sec_debug_get_reset_write_cnt);
char *sec_debug_get_reset_reason_str(unsigned int reason)
{
if (reason < USER_UPLOAD_CAUSE_MIN || reason > USER_UPLOAD_CAUSE_MAX)
reason = USER_UPLOAD_CAUSE_UNKNOWN;
return rr_str[reason];
}
EXPORT_SYMBOL(sec_debug_get_reset_reason_str);
static void sec_debug_update_reset_reason(uint32_t debug_partition_rr)
{
static char updated = 0;
if (!updated) {
reset_reason = debug_partition_rr;
updated = 1;
pr_info("partition[%d] result[%s]\n",
debug_partition_rr,
sec_debug_get_reset_reason_str(reset_reason));
}
}
static void reset_reason_update_and_clear(void)
{
ap_health_t *p_health;
uint32_t rr_data;
p_health = ap_health_data_read();
if (p_health == NULL) {
pr_err("p_health is NULL\n");
return;
}
pr_info("done\n");
rr_data = sec_debug_get_reset_reason();
switch (rr_data) {
case USER_UPLOAD_CAUSE_SMPL:
p_health->daily_rr.sp++;
p_health->rr.sp++;
break;
case USER_UPLOAD_CAUSE_WTSR:
p_health->daily_rr.wp++;
p_health->rr.wp++;
break;
case USER_UPLOAD_CAUSE_WATCHDOG:
p_health->daily_rr.dp++;
p_health->rr.dp++;
break;
case USER_UPLOAD_CAUSE_PANIC:
p_health->daily_rr.kp++;
p_health->rr.kp++;
break;
case USER_UPLOAD_CAUSE_MANUAL_RESET:
p_health->daily_rr.mp++;
p_health->rr.mp++;
break;
case USER_UPLOAD_CAUSE_POWER_RESET:
p_health->daily_rr.pp++;
p_health->rr.pp++;
break;
case USER_UPLOAD_CAUSE_REBOOT:
p_health->daily_rr.rp++;
p_health->rr.rp++;
break;
case USER_UPLOAD_CAUSE_THERMAL:
p_health->daily_rr.tp++;
p_health->rr.tp++;
break;
default:
p_health->daily_rr.np++;
p_health->rr.np++;
}
p_health->last_rst_reason = 0;
ap_health_data_write(p_health);
}
static int set_reset_reason_proc_show(struct seq_file *m, void *v)
{
uint32_t rr_data = sec_debug_get_reset_reason();
static uint32_t rr_cnt_update = 1;
seq_printf(m, "%sON\n", sec_debug_get_reset_reason_str(rr_data));
if (rr_cnt_update) {
reset_reason_update_and_clear();
rr_cnt_update = 0;
}
return 0;
}
static int sec_reset_reason_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, set_reset_reason_proc_show, NULL);
}
static const struct file_operations sec_reset_reason_proc_fops = {
.open = sec_reset_reason_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static phys_addr_t sec_debug_reset_ex_info_paddr;
static unsigned sec_debug_reset_ex_info_size;
rst_exinfo_t *sec_debug_reset_ex_info;
ex_info_fault_t ex_info_fault[NR_CPUS];
void sec_debug_store_extc_idx(bool prefix)
{
_kern_ex_info_t *p_ex_info;
if (sec_debug_reset_ex_info) {
p_ex_info = &sec_debug_reset_ex_info->kern_ex_info.info;
if (p_ex_info->extc_idx == 0) {
p_ex_info->extc_idx = get_sec_log_idx();
if (prefix)
p_ex_info->extc_idx += SEC_DEBUG_RESET_EXTRC_SIZE;
}
}
}
EXPORT_SYMBOL(sec_debug_store_extc_idx);
void sec_debug_store_bug_string(const char *fmt, ...)
{
va_list args;
_kern_ex_info_t *p_ex_info;
if (sec_debug_reset_ex_info) {
p_ex_info = &sec_debug_reset_ex_info->kern_ex_info.info;
va_start(args, fmt);
vsnprintf(p_ex_info->bug_buf,
sizeof(p_ex_info->bug_buf), fmt, args);
va_end(args);
}
}
EXPORT_SYMBOL(sec_debug_store_bug_string);
void sec_debug_store_additional_dbg(enum extra_info_dbg_type type,
unsigned int value, const char *fmt, ...)
{
va_list args;
_kern_ex_info_t *p_ex_info;
if (sec_debug_reset_ex_info) {
p_ex_info = &sec_debug_reset_ex_info->kern_ex_info.info;
switch (type) {
case DBG_0_GLAD_ERR:
va_start(args, fmt);
vsnprintf(p_ex_info->dbg0,
sizeof(p_ex_info->dbg0), fmt, args);
va_end(args);
break;
case DBG_1_UFS_ERR:
va_start(args, fmt);
vsnprintf(p_ex_info->ufs_err,
sizeof(p_ex_info->ufs_err), fmt, args);
va_end(args);
break;
case DBG_2_DISPLAY_ERR:
va_start(args, fmt);
vsnprintf(p_ex_info->display_err,
sizeof(p_ex_info->display_err), fmt, args);
va_end(args);
break;
case DBG_3_RESERVED ... DBG_5_RESERVED:
break;
default:
break;
}
}
}
EXPORT_SYMBOL(sec_debug_store_additional_dbg);
static void sec_debug_init_panic_extra_info(void)
{
_kern_ex_info_t *p_ex_info;
if (sec_debug_reset_ex_info) {
p_ex_info = &sec_debug_reset_ex_info->kern_ex_info.info;
memset((void *)&sec_debug_reset_ex_info->kern_ex_info, 0,
sizeof(sec_debug_reset_ex_info->kern_ex_info));
p_ex_info->cpu = -1;
pr_info("%s: ex_info memory initialized size[%ld]\n",
__func__, sizeof(kern_exinfo_t));
}
}
static int __init sec_debug_ex_info_setup(char *str)
{
unsigned size = memparse(str, &str);
int ret;
if (size && (*str == '@')) {
unsigned long long base = 0;
ret = kstrtoull(++str, 0, &base);
if (ret) {
pr_err("failed to parse sec_dbg_ex_info\n");
return ret;
}
sec_debug_reset_ex_info_paddr = base;
sec_debug_reset_ex_info_size =
(size + 0x1000 - 1) & ~(0x1000 - 1);
pr_info("ex info phy=0x%llx, size=0x%x\n",
(uint64_t)sec_debug_reset_ex_info_paddr,
sec_debug_reset_ex_info_size);
}
return 1;
}
__setup("sec_dbg_ex_info=", sec_debug_ex_info_setup);
static int __init sec_debug_get_extra_info_region(void)
{
if (!sec_debug_reset_ex_info_paddr || !sec_debug_reset_ex_info_size)
return -1;
sec_debug_reset_ex_info = ioremap_cache(sec_debug_reset_ex_info_paddr,
sec_debug_reset_ex_info_size);
if (!sec_debug_reset_ex_info) {
pr_err("Failed to remap nocache ex info region\n");
return -1;
}
sec_debug_init_panic_extra_info();
return 0;
}
arch_initcall_sync(sec_debug_get_extra_info_region);
struct debug_reset_header *get_debug_reset_header(void)
{
struct debug_reset_header *header = NULL;
static int get_state = DRH_STATE_INIT;
if (get_state == DRH_STATE_INVALID)
return NULL;
header = kmalloc(sizeof(struct debug_reset_header), GFP_KERNEL);
if (!header) {
pr_err("%s : fail - kmalloc for debug_reset_header\n", __func__);
return NULL;
}
if (!read_debug_partition(debug_index_reset_summary_info, header)) {
pr_err("%s : fail - get param!! debug_reset_header\n", __func__);
kfree(header);
header = NULL;
return NULL;
}
if (get_state != DRH_STATE_VALID) {
if (header->write_times == header->read_times) {
pr_err("%s : untrustworthy debug_reset_header\n", __func__);
get_state = DRH_STATE_INVALID;
kfree(header);
header = NULL;
return NULL;
}
reset_write_cnt = header->write_times;
get_state = DRH_STATE_VALID;
}
return header;
}
static int set_debug_reset_header(struct debug_reset_header *header)
{
int ret = 0;
static int set_state = DRH_STATE_INIT;
if (set_state == DRH_STATE_VALID) {
pr_info("%s : debug_reset_header working well\n", __func__);
return ret;
}
if ((header->write_times - 1) == header->read_times) {
pr_info("%s : debug_reset_header working well\n", __func__);
header->read_times++;
} else {
pr_info("%s : debug_reset_header read[%d] and write[%d] work sync error.\n",
__func__, header->read_times, header->write_times);
header->read_times = header->write_times;
}
if (!write_debug_partition(debug_index_reset_summary_info, header)) {
pr_err("%s : fail - set param!! debug_reset_header\n", __func__);
ret = -ENOENT;
} else {
set_state = DRH_STATE_VALID;
}
return ret;
}
static int sec_reset_summary_info_init(void)
{
int ret = 0;
if (summary_buf != NULL)
return true;
if (summary_info != NULL) {
pr_err("%s : already memory alloc for summary_info\n", __func__);
return -EINVAL;
}
summary_info = get_debug_reset_header();
if (summary_info == NULL)
return -EINVAL;
if (summary_info->summary_size > SEC_DEBUG_RESET_SUMMARY_SIZE) {
pr_err("%s : summary_size has problem.\n", __func__);
ret = -EINVAL;
goto error_summary_info;
}
summary_buf = vmalloc(SEC_DEBUG_RESET_SUMMARY_SIZE);
if (!summary_buf) {
pr_err("%s : fail - kmalloc for summary_buf\n", __func__);
ret = -ENOMEM;
goto error_summary_info;
}
if (!read_debug_partition(debug_index_reset_summary, summary_buf)) {
pr_err("%s : fail - get param!! summary data\n", __func__);
ret = -ENOENT;
goto error_summary_buf;
}
pr_info("%s : w[%d] r[%d] idx[%d] size[%d]\n",
__func__, summary_info->write_times, summary_info->read_times,
summary_info->ap_klog_idx, summary_info->summary_size);
return ret;
error_summary_buf:
vfree(summary_buf);
error_summary_info:
kfree(summary_info);
return ret;
}
static int sec_reset_summary_completed(void)
{
int ret = 0;
ret = set_debug_reset_header(summary_info);
vfree(summary_buf);
kfree(summary_info);
summary_info = NULL;
summary_buf = NULL;
pr_info("%s finish\n", __func__);
return ret;
}
static ssize_t sec_reset_summary_info_proc_read(struct file *file,
char __user *buf, size_t len, loff_t *offset)
{
loff_t pos = *offset;
ssize_t count;
mutex_lock(&summary_mutex);
if (sec_reset_summary_info_init() < 0) {
mutex_unlock(&summary_mutex);
return -ENOENT;
}
if ((pos >= summary_info->summary_size) || (pos >= SEC_DEBUG_RESET_SUMMARY_SIZE)) {
pr_info("%s : pos %lld, size %d\n", __func__, pos, summary_info->summary_size);
sec_reset_summary_completed();
mutex_unlock(&summary_mutex);
return 0;
}
count = min(len, (size_t)(summary_info->summary_size - pos));
if (copy_to_user(buf, summary_buf + pos, count)) {
mutex_unlock(&summary_mutex);
return -EFAULT;
}
*offset += count;
mutex_unlock(&summary_mutex);
return count;
}
static const struct file_operations sec_reset_summary_info_proc_fops = {
.owner = THIS_MODULE,
.read = sec_reset_summary_info_proc_read,
};
static int sec_reset_klog_init(void)
{
int ret = 0;
if ((klog_read_buf != NULL) && (klog_buf != NULL))
return true;
if (klog_info != NULL) {
pr_err("%s : already memory alloc for klog_info\n", __func__);
return -EINVAL;
}
klog_info = get_debug_reset_header();
if (klog_info == NULL)
return -EINVAL;
klog_read_buf = vmalloc(SEC_DEBUG_RESET_KLOG_SIZE);
if (!klog_read_buf) {
pr_err("%s : fail - vmalloc for klog_read_buf\n", __func__);
ret = -ENOMEM;
goto error_klog_info;
}
if (!read_debug_partition(debug_index_reset_klog, klog_read_buf)) {
pr_err("%s : fail - get param!! summary data\n", __func__);
ret = -ENOENT;
goto error_klog_read_buf;
}
pr_info("%s : idx[%d]\n", __func__, klog_info->ap_klog_idx);
klog_size = min((uint32_t)SEC_DEBUG_RESET_KLOG_SIZE, (uint32_t)klog_info->ap_klog_idx);
klog_buf = vmalloc(klog_size);
if (!klog_buf) {
pr_err("%s : fail - vmalloc for klog_buf\n", __func__);
ret = -ENOMEM;
goto error_klog_read_buf;
}
if (klog_size && klog_buf && klog_read_buf) {
unsigned int i;
for (i = 0; i < klog_size; i++)
klog_buf[i] = klog_read_buf[(klog_info->ap_klog_idx - klog_size + i) % SEC_DEBUG_RESET_KLOG_SIZE];
}
return ret;
error_klog_read_buf:
vfree(klog_read_buf);
error_klog_info:
kfree(klog_info);
return ret;
}
static void sec_reset_klog_completed(void)
{
set_debug_reset_header(klog_info);
vfree(klog_buf);
vfree(klog_read_buf);
kfree(klog_info);
klog_info = NULL;
klog_buf = NULL;
klog_read_buf = NULL;
klog_size = 0;
pr_info("%s finish\n", __func__);
}
static ssize_t sec_reset_klog_proc_read(struct file *file, char __user *buf,
size_t len, loff_t *offset)
{
loff_t pos = *offset;
ssize_t count;
mutex_lock(&klog_mutex);
if (sec_reset_klog_init() < 0) {
mutex_unlock(&klog_mutex);
return -ENOENT;
}
if (pos >= klog_size) {
pr_info("%s : pos %lld, size %d\n", __func__, pos, klog_size);
sec_reset_klog_completed();
mutex_unlock(&klog_mutex);
return 0;
}
count = min(len, (size_t)(klog_size - pos));
if (copy_to_user(buf, klog_buf + pos, count)) {
mutex_unlock(&klog_mutex);
return -EFAULT;
}
*offset += count;
mutex_unlock(&klog_mutex);
return count;
}
static const struct file_operations sec_reset_klog_proc_fops = {
.owner = THIS_MODULE,
.read = sec_reset_klog_proc_read,
};
static int sec_reset_tzlog_init(void)
{
int ret = 0;
if (tzlog_buf != NULL)
return true;
if (tzlog_info != NULL) {
pr_err("%s : already memory alloc for tzlog_info\n", __func__);
return -EINVAL;
}
tzlog_info = get_debug_reset_header();
if (tzlog_info == NULL)
return -EINVAL;
if (tzlog_info->stored_tzlog == 0) {
pr_err("%s : The target didn't run SDI operation\n", __func__);
ret = -EINVAL;
goto error_tzlog_info;
}
tzlog_buf = vmalloc(SEC_DEBUG_RESET_TZLOG_SIZE);
if (!tzlog_buf) {
pr_err("%s : fail - vmalloc for tzlog_read_buf\n", __func__);
ret = -ENOMEM;
goto error_tzlog_info;
}
if (!read_debug_partition(debug_index_reset_tzlog, tzlog_buf)) {
pr_err("%s : fail - get param!! tzlog data\n", __func__);
ret = -ENOENT;
goto error_tzlog_buf;
}
return ret;
error_tzlog_buf:
vfree(tzlog_buf);
error_tzlog_info:
kfree(tzlog_info);
return ret;
}
static void sec_reset_tzlog_completed(void)
{
set_debug_reset_header(tzlog_info);
vfree(tzlog_buf);
kfree(tzlog_info);
tzlog_info = NULL;
tzlog_buf = NULL;
pr_info("%s finish\n", __func__);
}
static ssize_t sec_reset_tzlog_proc_read(struct file *file, char __user *buf,
size_t len, loff_t *offset)
{
loff_t pos = *offset;
ssize_t count;
mutex_lock(&tzlog_mutex);
if (sec_reset_tzlog_init() < 0) {
mutex_unlock(&tzlog_mutex);
return -ENOENT;
}
if (pos >= SEC_DEBUG_RESET_TZLOG_SIZE) {
pr_info("%s : pos %lld, size %d\n", __func__, pos, SEC_DEBUG_RESET_TZLOG_SIZE);
sec_reset_tzlog_completed();
mutex_unlock(&tzlog_mutex);
return 0;
}
count = min(len, (size_t)(SEC_DEBUG_RESET_TZLOG_SIZE - pos));
if (copy_to_user(buf, tzlog_buf + pos, count)) {
mutex_unlock(&tzlog_mutex);
return -EFAULT;
}
*offset += count;
mutex_unlock(&tzlog_mutex);
return count;
}
static const struct file_operations sec_reset_tzlog_proc_fops = {
.owner = THIS_MODULE,
.read = sec_reset_tzlog_proc_read,
};
static int set_store_lastkmsg_proc_show(struct seq_file *m, void *v)
{
struct debug_reset_header *check_store = NULL;
if (check_store != NULL) {
pr_err("%s : already memory alloc for check_store\n", __func__);
return -EINVAL;
}
check_store = get_debug_reset_header();
if (check_store == NULL) {
seq_printf(m, "0\n");
} else {
seq_printf(m, "1\n");
}
if (check_store != NULL) {
kfree(check_store);
check_store = NULL;
}
return 0;
}
static int sec_store_lastkmsg_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, set_store_lastkmsg_proc_show, NULL);
}
static const struct file_operations sec_store_lastkmsg_proc_fops = {
.open = sec_store_lastkmsg_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static void sec_restore_modem_reset_data(void)
{
void *p_modem = sec_debug_summary_get_modem();
struct debug_reset_header *header = get_debug_reset_header();
if (!header) {
pr_info("%s : updated nothing.\n", __func__);
return;
}
if (sec_debug_get_reset_reason() != USER_UPLOAD_CAUSE_PANIC) {
pr_info("%s : it was not kernel panic.\n", __func__);
return;
}
if (p_modem) {
read_debug_partition(debug_index_modem_info, p_modem);
pr_info("%s : complete.\n", __func__);
} else {
pr_info("%s : skip.\n", __func__);
}
}
void sec_debug_summary_modem_print(void)
{
if (sec_debug_get_reset_reason() != USER_UPLOAD_CAUSE_PANIC) {
pr_info("%s : it was not kernel panic.\n", __func__);
return;
}
pr_info("0x%016lx\n",
(unsigned long)sec_debug_summary_get_modem());
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1,
sec_debug_summary_get_modem(),
0x190, 1);
}
EXPORT_SYMBOL(sec_debug_summary_modem_print);
static int sec_reset_reason_dbg_part_notifier_callback(
struct notifier_block *nfb, unsigned long action, void *data)
{
ap_health_t *p_health;
uint32_t rr_data;
switch (action) {
case DBG_PART_DRV_INIT_DONE:
p_health = ap_health_data_read();
if (!p_health)
return NOTIFY_DONE;
sec_debug_update_reset_reason(
p_health->last_rst_reason);
rr_data = sec_debug_get_reset_reason();
sec_restore_modem_reset_data();
break;
default:
return NOTIFY_DONE;
}
return NOTIFY_OK;
}
static struct notifier_block sec_reset_reason_dbg_part_notifier = {
.notifier_call = sec_reset_reason_dbg_part_notifier_callback,
};
static int __init sec_debug_reset_reason_init(void)
{
struct proc_dir_entry *entry;
entry = proc_create("reset_reason", S_IWUGO, NULL,
&sec_reset_reason_proc_fops);
if (unlikely(!entry))
return -ENOMEM;
entry = proc_create("reset_summary", S_IWUGO, NULL,
&sec_reset_summary_info_proc_fops);
if (unlikely(!entry))
return -ENOMEM;
entry = proc_create("reset_klog", S_IWUGO, NULL,
&sec_reset_klog_proc_fops);
if (unlikely(!entry))
return -ENOMEM;
entry = proc_create("reset_tzlog", S_IWUGO, NULL,
&sec_reset_tzlog_proc_fops);
if (unlikely(!entry))
return -ENOMEM;
entry = proc_create("store_lastkmsg", S_IWUGO, NULL,
&sec_store_lastkmsg_proc_fops);
if (unlikely(!entry))
return -ENOMEM;
dbg_partition_notifier_register(&sec_reset_reason_dbg_part_notifier);
return 0;
}
device_initcall(sec_debug_reset_reason_init);
static ssize_t show_recovery_cause(struct device *dev,
struct device_attribute *attr, char *buf)
{
char recovery_cause[256];
sec_get_param(param_index_reboot_recovery_cause, recovery_cause);
pr_info("%s\n", recovery_cause);
return scnprintf(buf, sizeof(recovery_cause), "%s", recovery_cause);
}
static ssize_t store_recovery_cause(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
char recovery_cause[256];
if (count > sizeof(recovery_cause)) {
pr_err("input buffer length is out of range.\n");
return -EINVAL;
}
snprintf(recovery_cause, sizeof(recovery_cause), "%s:%d ", current->comm, task_pid_nr(current));
if (strlen(recovery_cause) + strlen(buf) >= sizeof(recovery_cause)) {
pr_err("input buffer length is out of range.\n");
return -EINVAL;
}
strncat(recovery_cause, buf, strlen(buf));
sec_set_param(param_index_reboot_recovery_cause, recovery_cause);
pr_info("%s\n", recovery_cause);
return count;
}
static DEVICE_ATTR(recovery_cause, 0660, show_recovery_cause, store_recovery_cause);
static struct device *sec_debug_dev;
static int __init sec_debug_recovery_reason_init(void)
{
int ret;
/* create sysfs for reboot_recovery_cause */
sec_debug_dev = sec_device_create(0, NULL, "sec_debug");
if (IS_ERR(sec_debug_dev)) {
pr_err("Failed to create device for sec_debug\n");
return PTR_ERR(sec_debug_dev);
}
ret = sysfs_create_file(&sec_debug_dev->kobj, &dev_attr_recovery_cause.attr);
if (ret) {
pr_err("Failed to create sysfs group for sec_debug\n");
sec_device_destroy(sec_debug_dev->devt);
sec_debug_dev = NULL;
return ret;
}
return 0;
}
device_initcall(sec_debug_recovery_reason_init);
void _sec_debug_store_backtrace(unsigned long where)
{
static int offset;
unsigned int max_size = 0;
_kern_ex_info_t *p_ex_info;
if (sec_debug_reset_ex_info) {
p_ex_info = &sec_debug_reset_ex_info->kern_ex_info.info;
max_size = (unsigned long long int)&sec_debug_reset_ex_info->rpm_ex_info.info
- (unsigned long long int)p_ex_info->backtrace;
if (max_size <= offset)
return;
if (offset)
offset += snprintf(p_ex_info->backtrace+offset,
max_size-offset, " > ");
offset += snprintf(p_ex_info->backtrace+offset, max_size-offset,
"%pS", (void *)where);
}
}
void sec_debug_backtrace(void)
{
static int once = 0;
struct stackframe frame;
int skip_callstack = 0;
if (!once++) {
frame.fp = (unsigned long)__builtin_frame_address(0);
frame.sp = current_stack_pointer;
frame.pc = (unsigned long)sec_debug_backtrace;
while (1) {
int ret;
ret = unwind_frame(current, &frame);
if (ret < 0)
break;
if (skip_callstack++ > 3) {
_sec_debug_store_backtrace(frame.pc);
}
}
}
}
EXPORT_SYMBOL(sec_debug_backtrace);

View File

@@ -0,0 +1,60 @@
// SPDX-License-Identifier: GPL-2.0
/*
* drivers/samsung/sec_kcompat.c
*
* COPYRIGHT(C) 2019 Samsung Electronics Co., Ltd. All Right Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only 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.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__
#include <linux/bitmap.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#if defined(CONFIG_MSM_SMEM)
#include <soc/qcom/smem.h>
#endif
#include "sec_kcompat.h"
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,14,0)
#if defined(CONFIG_MSM_SMEM)
void * __weak qcom_smem_get(unsigned host, unsigned item, size_t *size)
{
void * ret;
unsigned int size_tmp = 0;
ret = smem_get_entry(item, &size_tmp, SMEM_APPS, host);
*size = (size_t)size_tmp;
return ret;
}
phys_addr_t __weak qcom_smem_virt_to_phys(void *p)
{
return smem_virt_to_phys(p) & 0xFFFFFFFFULL;
}
#endif /* CONFIG_MSM_SMEM */
#endif /* KERNEL_VERSION(4,14,0) */
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,19,0)
unsigned long * __weak bitmap_alloc(unsigned int nbits, gfp_t flags)
{
return kmalloc_array(BITS_TO_LONGS(nbits), sizeof(unsigned long),
flags);
}
unsigned long * __weak bitmap_zalloc(unsigned int nbits, gfp_t flags)
{
return bitmap_alloc(nbits, flags | __GFP_ZERO);
}
#endif /* KERNEL_VERSION(4,19,0) */

View File

@@ -0,0 +1,34 @@
#ifndef __SEC_KCOMPAT_H__
#define __SEC_KCOMPAT_H__
#include <linux/version.h>
#if defined(CONFIG_MSM_SMEM) && defined(CONFIG_QCOM_SMEM)
#error "CONFIG_MSM_SMEM and CONFIG_QCOM_SMEM can not be enabled at the same time"
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,14,0)
#define timer_setup(__timer, __fn, __data) setup_timer(__timer, __fn, __data)
#if defined(CONFIG_MSM_SMEM)
#if defined(QCOM_SMEM_HOST_ANY)
#undef QCOM_SMEM_HOST_ANY
#endif
#define QCOM_SMEM_HOST_ANY SMEM_ANY_HOST_FLAG
void *qcom_smem_get(unsigned host, unsigned item, size_t *size);
extern phys_addr_t qcom_smem_virt_to_phys(void *p);
#endif /* CONFIG_MSM_SMEM */
#endif /* KERNEL_VERSION(4,14,0) */
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,19,0)
extern unsigned long *bitmap_alloc(unsigned int nbits, gfp_t flags);
extern unsigned long *bitmap_zalloc(unsigned int nbits, gfp_t flags);
#endif /* KERNEL_VERSION(4,19,0) */
#endif /* __SEC_KCOMPAT_H__ */

View File

@@ -0,0 +1,170 @@
// SPDX-License-Identifier: GPL-2.0
/*
* drivers/samsung/debug/sec_key_notifier.c
*
* COPYRIGHT(C) 2016-2019 Samsung Electronics Co., Ltd. All Right Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only 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.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":%s() " fmt, __func__
#include <linux/module.h>
#include <linux/input.h>
#include <linux/notifier.h>
#include <linux/slab.h>
#include <linux/sec_debug.h>
#include "sec_key_notifier.h"
static DEFINE_SPINLOCK(sec_kn_event_lock);
static ATOMIC_NOTIFIER_HEAD(sec_kn_notifier_list);
static atomic_t sec_kn_acceptable_event[KEY_MAX] __read_mostly;
static void inline update_acceptable_event(unsigned int event_code, bool is_add)
{
if (is_add)
atomic_inc(&(sec_kn_acceptable_event[event_code]));
else
atomic_dec(&(sec_kn_acceptable_event[event_code]));
}
int sec_kn_register_notifier(struct notifier_block *nb,
const unsigned int *events, const size_t nr_events)
{
size_t i;
for (i = 0; i < nr_events; i++)
update_acceptable_event(events[i], true);
return atomic_notifier_chain_register(&sec_kn_notifier_list, nb);
}
int sec_kn_unregister_notifier(struct notifier_block *nb,
const unsigned int *events, const size_t nr_events)
{
size_t i;
for (i = 0; i < nr_events; i++)
update_acceptable_event(events[i], false);
return atomic_notifier_chain_unregister(&sec_kn_notifier_list, nb);
}
static inline bool is_event_supported(unsigned int event_type,
unsigned int event_code)
{
bool ret;
if (event_type != EV_KEY || event_code >= KEY_MAX)
return false;
ret = !!atomic_read(&(sec_kn_acceptable_event[event_code]));
return ret;
}
static void sec_kn_event(struct input_handle *handle, unsigned int event_type,
unsigned int event_code, int value)
{
struct sec_key_notifier_param param = {
.keycode = event_code,
.down = value,
};
if (!is_event_supported(event_type, event_code))
return;
spin_lock(&sec_kn_event_lock);
atomic_notifier_call_chain(&sec_kn_notifier_list, 0, &param);
spin_unlock(&sec_kn_event_lock);
}
static int sec_kn_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct input_handle *handle;
int error;
handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
if (!handle)
return -ENOMEM;
handle->dev = dev;
handle->handler = handler;
handle->name = "sec_key_notifier";
error = input_register_handle(handle);
if (error)
goto err_free_handle;
error = input_open_device(handle);
if (error)
goto err_unregister_handle;
return 0;
err_unregister_handle:
input_unregister_handle(handle);
err_free_handle:
kfree(handle);
return error;
}
static void sec_kn_disconnect(struct input_handle *handle)
{
input_close_device(handle);
input_unregister_handle(handle);
kfree(handle);
}
static const struct input_device_id sec_kn_ids[] = {
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
.evbit = { BIT_MASK(EV_KEY) },
},
{},
};
static struct input_handler sec_kn_handler = {
.event = sec_kn_event,
.connect = sec_kn_connect,
.disconnect = sec_kn_disconnect,
.name = "sec_key_notifier",
.id_table = sec_kn_ids,
};
static int __init sec_kn_init(void)
{
int err;
size_t i;
for (i = 0; i < KEY_MAX; i++)
atomic_set(&(sec_kn_acceptable_event[i]), 0);
spin_lock_init(&sec_kn_event_lock);
err = input_register_handler(&sec_kn_handler);
return err;
}
static void __exit sec_kn_exit(void)
{
input_unregister_handler(&sec_kn_handler);
}
arch_initcall(sec_kn_init);
module_exit(sec_kn_exit);

View File

@@ -0,0 +1,35 @@
/*
* drivers/debug/sec_key_notifier.h
*
* COPYRIGHT(C) 2016 Samsung Electronics Co., Ltd. All Right Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __SEC_KEY_NOTIFIER_H__
#define __SEC_KEY_NOTIFIER_H__
struct sec_key_notifier_param {
unsigned int keycode;
int down;
};
int sec_kn_register_notifier(struct notifier_block *nb,
const unsigned int *events, const size_t nr_events);
int sec_kn_unregister_notifier(struct notifier_block *nb,
const unsigned int *events, const size_t nr_events);
#endif /* __SEC_KEY_NOTIFIER_H__ */

1719
drivers/debug/sec_nad.c Normal file

File diff suppressed because it is too large Load Diff

2457
drivers/debug/sec_quest.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,522 @@
/*
* File is for internal use only, and not for distribution
* If distribution is needed, OS review is required.
* See OSRQCT-5220 for further information.
*/
#define pr_fmt(fmt) "spinlock-test: " fmt
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/workqueue.h>
#include <linux/cpu.h>
#include <linux/debugfs.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/ktime.h>
#include <linux/dma-mapping.h>
#include <linux/version.h>
#define MODULE_NAME "spinlock_test"
#define CPU_NUM_MAX NR_CPUS
#define TEST_ITERS_MAX 100
#define INNER_TEST_TIME_SECS_DEFAULT 5
#define INNER_TEST_TIME_SECS_MAX 100
#define SPINLOCK_TEST_DELAY_MS 1000
#define IRQ_DIALBE_TIME_MS (MSEC_PER_SEC/HZ)
static void spinwork_fn(struct work_struct *work);
static struct delayed_work spinworks[CPU_NUM_MAX];
static struct dentry *dent;
static DEFINE_PER_CPU(int, cpu_test_idx);
static u32 start;
static u32 num_test_iters;
static u32 canceltest;
static u32 cpu_start[CPU_NUM_MAX];
static u32 inner_test_time_secs = INNER_TEST_TIME_SECS_DEFAULT;
static struct locks {
spinlock_t lock1;
spinlock_t lock2;
} testlocks __cacheline_aligned;
/*
* global_lock_static will be in the kernel static (BSS?) region.
* global_inner_lock will be kmalloc'ed. The idea here is to use
* different cache-lines for the two locks.
*/
static DEFINE_SPINLOCK(global_lock_static);
static spinlock_t *global_lock_heap;
static spinlock_t *uncached_lock;
static phys_addr_t phys;
enum {
IRQS_DISABLED_TEST = 1,
STACK_LOCK_TEST,
TWOLOCKS_TEST,
SIMPLE_TEST,
UNCACHED_LOCK_TEST,
NUM_OF_SPINLOCK_TEST = 5,
};
static int set_inner_test_time(void *data, u64 val)
{
if (val != 0) {
inner_test_time_secs = val < INNER_TEST_TIME_SECS_MAX ? \
val : INNER_TEST_TIME_SECS_MAX;
pr_debug("set_inner_test_time: %u\n", inner_test_time_secs);
}
return 0;
}
static int get_inner_test_time(void *data, u64 *val)
{
*val = inner_test_time_secs;
return 0;
}
static int spinlock_test_start(void);
static int spinlock_test_stop(void);
static int set_spinlock_start(void *data, u64 val)
{
int ret = 0;
if (val > 0) {
if (start) {
pr_err("The last test is not done yet. Exitting...\n");
return 0;
}
num_test_iters = val < TEST_ITERS_MAX ? val : TEST_ITERS_MAX;
ret = spinlock_test_start();
} else if (val == 0) {
if (!start) {
pr_err("No spinlock test is running. Exitting...\n");
return 0;
}
ret = spinlock_test_stop();
}
return ret;
}
static int get_spinlock_start(void *data, u64 *val)
{
*val = start;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(spinlock_start_ops,
get_spinlock_start, set_spinlock_start, "%llu\n");
DEFINE_SIMPLE_ATTRIBUTE(inner_test_time_ops,
get_inner_test_time, set_inner_test_time, "%llu\n");
static int creat_spinlock_debugfs(void)
{
/*
* Create a simple debugfs with the name of "spinlock-test",
*
* As this is a simple directory, no uevent will be sent to
* userspace.
*/
struct dentry *phandle;
dent = debugfs_create_dir("spinlock-test", 0);
if (IS_ERR(dent))
return -ENOMEM;
phandle = debugfs_create_file("start", 0666, dent, 0, &spinlock_start_ops);
if (!phandle || IS_ERR(phandle))
goto err;
phandle = debugfs_create_file("inner_test_time", 0666, dent, 0, &inner_test_time_ops);
if (!phandle || IS_ERR(phandle))
goto err;
return 0;
err:
debugfs_remove_recursive(dent);
return -ENOMEM;
}
static void sync_cpus(int test_idx, int cpu)
{
int ocpu, flag;
per_cpu(cpu_test_idx, cpu) = test_idx;
/*
Wait until all other cpus finished the same test case
within the same iteration.
*/
do {
flag = 0;
for_each_online_cpu(ocpu) {
if (per_cpu(cpu_test_idx, ocpu) != test_idx ||
cpu_start[ocpu] != cpu_start[cpu]) {
flag = 1;
break;
}
}
} while (flag);
}
static void irqs_disabled_spintest(int cpu)
{
unsigned long flags, irqflags;
int incritical = 0;
ktime_t test_time = ktime_add_us(ktime_get(),
(inner_test_time_secs * USEC_PER_SEC));
pr_info("(irq_disabled): Running test on CPU %d for %d seconds.\n", cpu,
inner_test_time_secs);
while (ktime_compare(ktime_get(), test_time) < 0)
{
ktime_t irq_disable_test_time = ktime_add_us(ktime_get(),
(IRQ_DIALBE_TIME_MS * USEC_PER_MSEC));
local_irq_save(irqflags);
while (ktime_compare(ktime_get(), irq_disable_test_time) < 0) {
spin_lock_irqsave(&global_lock_static, flags);
spin_lock(global_lock_heap);
incritical++;
spin_unlock(global_lock_heap);
spin_unlock_irqrestore(&global_lock_static, flags);
}
local_irq_restore(irqflags);
}
pr_info("(irqs disabled) CPU%d entered critical section %d times.\n",
cpu, incritical);
sync_cpus(IRQS_DISABLED_TEST, cpu);
}
static void spintest_two_lock_single_cacheline(int cpu)
{
unsigned long flags;
int incritical = 0;
ktime_t test_time = ktime_add_us(ktime_get(),
(inner_test_time_secs * USEC_PER_SEC));
pr_info("(two-locks): Running test on CPU %d for %d seconds.\n", cpu,
inner_test_time_secs);
while (ktime_compare(ktime_get(), test_time) < 0)
{
spin_lock_irqsave(&testlocks.lock1, flags);
spin_lock(&testlocks.lock2);
incritical++;
spin_unlock(&testlocks.lock2);
spin_unlock_irqrestore(&testlocks.lock1, flags);
}
pr_info("(two-locks): CPU%d entered critical section %d times.\n",
cpu, incritical);
sync_cpus(TWOLOCKS_TEST, cpu);
}
static void spintest_simple(int cpu)
{
unsigned long flags;
int incritical = 0;
ktime_t test_time = ktime_add_us(ktime_get(),
(inner_test_time_secs * USEC_PER_SEC));
pr_info("(simple): Running test on CPU %d for %d seconds.\n", cpu,
inner_test_time_secs);
while (ktime_compare(ktime_get(), test_time) < 0)
{
spin_lock_irqsave(&global_lock_static, flags);
incritical++;
spin_unlock_irqrestore(&global_lock_static, flags);
}
pr_info("(simple): CPU%d entered critical section %d times.\n",
cpu, incritical);
sync_cpus(SIMPLE_TEST, cpu);
}
static void spintest_stack_lock(int cpu)
{
unsigned long flags;
int incritical = 0;
ktime_t test_time = ktime_add_us(ktime_get(),
(inner_test_time_secs * USEC_PER_SEC));
spinlock_t stack_lock;
spin_lock_init(&stack_lock);
pr_info("(stack-lock): Running test on CPU %d for %d seconds.\n", cpu,
inner_test_time_secs);
while (ktime_compare(ktime_get(), test_time) < 0)
{
spin_lock_irqsave(&global_lock_static, flags);
spin_lock(&stack_lock);
incritical++;
spin_unlock(&stack_lock);
spin_unlock_irqrestore(&global_lock_static, flags);
}
pr_info("(stack-lock): CPU%d entered critical section %d times.\n",
cpu, incritical);
sync_cpus(STACK_LOCK_TEST, cpu);
}
static void spintest_uncached_lock_test(int cpu)
{
unsigned long flags;
int incritical = 0;
ktime_t test_time = ktime_add_us(ktime_get(),
(inner_test_time_secs * USEC_PER_SEC));
pr_info("(uncached-lock): Running test on CPU %d for %d seconds.\n", cpu,
inner_test_time_secs);
while (ktime_compare(ktime_get(), test_time) < 0)
{
spin_lock_irqsave(&global_lock_static, flags);
spin_lock(uncached_lock);
incritical++;
spin_unlock(uncached_lock);
spin_unlock_irqrestore(&global_lock_static, flags);
}
pr_info("(uncached-lock): CPU%d entered critical section %d times.\n",
cpu, incritical);
sync_cpus(UNCACHED_LOCK_TEST, cpu);
}
static void spinwork_fn(struct work_struct *work)
{
struct delayed_work *dwork = (struct delayed_work *)work;
int cpu = smp_processor_id();
if (canceltest) {
pr_err("test cancelled!\n");
return;
}
/*
* Start interrupt disabled test. This should provide maximum
* contention.
*/
irqs_disabled_spintest(cpu);
pr_info("irqs_disabled_spintest is done (CPU:%d iteration:%d)\n",
cpu, cpu_start[cpu]);
msleep(SPINLOCK_TEST_DELAY_MS);
spintest_simple(cpu);
pr_info("spintest_simple is done (CPU:%d iteration:%d)\n",
cpu, cpu_start[cpu]);
msleep(SPINLOCK_TEST_DELAY_MS);
/* Use a spinlock allocated on the stack */
spintest_stack_lock(cpu);
pr_info("spintest_stack_lock is done (CPU:%d iteration:%d)\n",
cpu, cpu_start[cpu]);
msleep(SPINLOCK_TEST_DELAY_MS);
spintest_two_lock_single_cacheline(cpu);
pr_info("spintest_two_lock is done (CPU:%d iteration:%d)\n",
cpu, cpu_start[cpu]);
msleep(SPINLOCK_TEST_DELAY_MS);
spintest_uncached_lock_test(cpu);
pr_info("spintest_uncached_lock_test is done (CPU:%d iteration:%d)\n",
cpu, cpu_start[cpu]);
msleep(SPINLOCK_TEST_DELAY_MS);
if (++cpu_start[cpu] < num_test_iters) {
schedule_work_on(cpu, &dwork->work);
}
}
static int spinlock_test_start(void)
{
int i, flag;
ktime_t test_time;
unsigned long total_wait_time;
start = 1;
canceltest = 0;
pr_info("Starting spinlock test\n");
#ifdef CONFIG_HOTPLUG_CPU
/* Online all the cores */
for_each_present_cpu(i) {
if (!cpu_online(i)) {
cpu_up(i);
}
}
#endif
get_online_cpus();
for_each_online_cpu(i) {
cpu_start[i] = 0;
INIT_DEFERRABLE_WORK(&spinworks[i], spinwork_fn);
schedule_delayed_work_on(i, &spinworks[i], msecs_to_jiffies(150));
pr_info("Scheduling spinlock test on cpu %d\n", i);
}
/*
* Estimate and calculate total wait time according to
* - number of toal test cases. (e.g irq_disable, stack spinlock, etc..)
* - inner test time for each test case
* - number of iterations for each test case
* - sleep time during two test cases
*/
total_wait_time = (NUM_OF_SPINLOCK_TEST + 1) * (inner_test_time_secs * \
num_test_iters + SPINLOCK_TEST_DELAY_MS / MSEC_PER_SEC);
pr_info("Total test time might be ~%lu seconds\n", total_wait_time);
test_time = ktime_add_us(ktime_get(),
(total_wait_time * USEC_PER_SEC));
while (ktime_compare(ktime_get(), test_time) < 0) {
flag = 0;
for_each_online_cpu(i) {
if (cpu_start[i] != num_test_iters) {
flag = 1;
break;
}
}
if (!flag) {
start = 0;
break;
}
msleep(SPINLOCK_TEST_DELAY_MS);
}
if (!start) {
pr_info("spinlock test is done successfully!\n");
put_online_cpus();
return 0;
} else {
pr_err("spinlock test Failed!\n");
spinlock_test_stop();
return -1;
}
}
static int spinlock_test_stop(void)
{
int i;
canceltest = 1;
if (!start) {
pr_err("No spinlock test is running. Exitting...\n");
return -1;
}
for(i = 0; i < CPU_NUM_MAX; i++){
if (spinworks[i].wq != NULL) {
pr_info("canceling work oncpu %d\n", i);
cancel_delayed_work_sync(&spinworks[i]);
}
}
/* "start" might be updated by the test work thread */
if (start)
put_online_cpus();
start = 0;
return 0;
}
static int spinlock_test_remove(struct platform_device *pdev)
{
/* Cancel the test if it's not done yet */
if (start)
spinlock_test_stop();
if (dent) {
debugfs_remove_recursive(dent);
dent = NULL;
}
if (global_lock_heap) {
kfree(global_lock_heap);
global_lock_heap = NULL;
}
if (uncached_lock) {
dma_free_coherent(&pdev->dev, sizeof(spinlock_t), uncached_lock, phys);
uncached_lock = NULL;
}
return 0;
}
static int spinlock_test_probe(struct platform_device *pdev)
{
int ret = 0;
if (num_possible_cpus() > CPU_NUM_MAX) {
dev_err(&pdev->dev, "Number of possible cpus on this target \
exceeds the limit %d\n", CPU_NUM_MAX);
return -EINVAL;
}
global_lock_heap = kzalloc(sizeof(spinlock_t), GFP_KERNEL);
if (!global_lock_heap) {
dev_err(&pdev->dev, "unable to alloc memory for global lock\n");
return -ENOMEM;
}
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,18,0))
arch_setup_dma_ops(&pdev->dev, 0, U64_MAX, NULL, 0);
#endif
dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64));
uncached_lock = dma_alloc_coherent(&pdev->dev, sizeof(spinlock_t), &phys, GFP_KERNEL);
if (!uncached_lock) {
dev_err(&pdev->dev ,"Failed to allocate Uncached memory for lock\n");
kfree(global_lock_heap);
return -ENOMEM;
}
spin_lock_init(&testlocks.lock1);
spin_lock_init(&testlocks.lock2);
spin_lock_init(global_lock_heap);
spin_lock_init(uncached_lock);
ret = creat_spinlock_debugfs();
if (ret) {
dev_err(&pdev->dev, "create spinlock debugfs failed!\n");
kfree(global_lock_heap);
dma_free_coherent(&pdev->dev, sizeof(spinlock_t), uncached_lock, phys);
uncached_lock = NULL;
return ret;
}
return 0;
}
static struct platform_driver spinlock_test_driver = {
.probe = spinlock_test_probe,
.remove = spinlock_test_remove,
.driver = {
.name = MODULE_NAME,
.owner = THIS_MODULE,
},
};
static void platform_spinlock_release(struct device* dev)
{
return;
}
static struct platform_device spinlock_test_device = {
.name = MODULE_NAME,
.id = -1,
.dev = {
.release = platform_spinlock_release,
}
};
static int __init spinlock_test_init(void)
{
platform_device_register(&spinlock_test_device);
return platform_driver_register(&spinlock_test_driver);
}
static void __exit spinlock_test_exit(void)
{
platform_driver_unregister(&spinlock_test_driver);
platform_device_unregister(&spinlock_test_device);
}
MODULE_DESCRIPTION("SPINLOCK TEST");
MODULE_LICENSE("GPL v2");
module_init(spinlock_test_init);
module_exit(spinlock_test_exit);

View File

@@ -224,7 +224,7 @@ struct extcon_cable {
};
static struct class *extcon_class;
#if defined(CONFIG_ANDROID)
#if defined(CONFIG_ANDROID) && !IS_ENABLED(CONFIG_SWITCH)
static struct class_compat *switch_class;
#endif /* CONFIG_ANDROID */
@@ -1011,7 +1011,7 @@ static int create_extcon_class(void)
return PTR_ERR(extcon_class);
extcon_class->dev_groups = extcon_groups;
#if defined(CONFIG_ANDROID)
#if defined(CONFIG_ANDROID) && !IS_ENABLED(CONFIG_SWITCH)
switch_class = class_compat_register("switch");
if (WARN(!switch_class, "cannot allocate"))
return -ENOMEM;
@@ -1237,7 +1237,7 @@ int extcon_dev_register(struct extcon_dev *edev)
put_device(&edev->dev);
goto err_dev;
}
#if defined(CONFIG_ANDROID)
#if defined(CONFIG_ANDROID) && !IS_ENABLED(CONFIG_SWITCH)
if (switch_class)
ret = class_compat_create_link(switch_class, &edev->dev, NULL);
#endif /* CONFIG_ANDROID */
@@ -1333,7 +1333,7 @@ void extcon_dev_unregister(struct extcon_dev *edev)
kfree(edev->cables);
}
#if defined(CONFIG_ANDROID)
#if defined(CONFIG_ANDROID) && !IS_ENABLED(CONFIG_SWITCH)
if (switch_class)
class_compat_remove_link(switch_class, &edev->dev, NULL);
#endif
@@ -1407,7 +1407,7 @@ module_init(extcon_class_init);
static void __exit extcon_class_exit(void)
{
#if defined(CONFIG_ANDROID)
#if defined(CONFIG_ANDROID) && !IS_ENABLED(CONFIG_SWITCH)
class_compat_unregister(switch_class);
#endif
class_destroy(extcon_class);

32
drivers/fingerprint/Kconfig Executable file
View File

@@ -0,0 +1,32 @@
#
# Sensor drivers configuration
#
menuconfig SENSORS_FINGERPRINT
bool "Finger Print Sensor devices"
help
Say Y here, and a list of sensors drivers will be displayed.
Everything that didn't fit into the other categories is here. This option
doesn't affect the kernel.
if SENSORS_FINGERPRINT
config SENSORS_VFS8XXX
tristate "VFS8XXX fingerprint sensor support"
default n
help
If you say yes here you get support for Synaptics's
fingerprint sensor NAMSAN.
config SENSORS_ET5XX
tristate "ET5XX fingerprint sensor support"
default n
help
If you say yes here you get support for Egistec's
fingerprint sensor ET5XX.
config SENSORS_GW32X
tristate "generic goodix fingerprint driver"
default n
help
add support for goodix fingerprint driver.
endif # SENSORS_FINGERPRINT

12
drivers/fingerprint/Makefile Executable file
View File

@@ -0,0 +1,12 @@
#
# Makefile for the sensors drivers.
#
# Each configuration option enables a list of files.
ccflags-y := $(KBUILD_FP_SENSOR_CFLAGS)
obj-$(CONFIG_SENSORS_FINGERPRINT) += fingerprint_sysfs.o
obj-$(CONFIG_SENSORS_VFS8XXX) += vfs8xxx.o
obj-$(CONFIG_SENSORS_ET5XX) += et5xx-spi.o et5xx-spi_data_transfer.o
obj-$(CONFIG_SENSORS_GW32X) += gf_common.o gf_platform.o

1398
drivers/fingerprint/et5xx-spi.c Executable file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

240
drivers/fingerprint/et5xx.h Executable file
View File

@@ -0,0 +1,240 @@
/*
* Copyright (C) 2016 Samsung Electronics. All rights reserved.
*
* 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.
*/
#ifndef _ET5XX_LINUX_DIRVER_H_
#define _ET5XX_LINUX_DIRVER_H_
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/wakelock.h>
#ifdef ENABLE_SENSORS_FPRINT_SECURE
#include <linux/clk.h>
#include <linux/pm_runtime.h>
#include <linux/spi/spidev.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_dma.h>
#include <linux/amba/bus.h>
#include <linux/amba/pl330.h>
#endif
#include <linux/cpufreq.h>
#include <linux/pinctrl/consumer.h>
#include "../pinctrl/core.h"
#include <linux/pm_qos.h>
/*#define ET5XX_SPI_DEBUG*/
#ifdef ET5XX_SPI_DEBUG
#define DEBUG_PRINT(fmt, args...) pr_err(fmt, ## args)
#else
#define DEBUG_PRINT(fmt, args...)
#endif
#define VENDOR "EGISTEC"
#define CHIP_ID "ET5XX"
/* assigned */
#define ET5XX_MAJOR 152
/* ... up to 256 */
#define N_SPI_MINORS 32
#define OP_REG_R 0x20
#define OP_REG_R_C 0x22
#define OP_REG_R_C_BW 0x23
#define OP_REG_W 0x24
#define OP_REG_W_C 0x26
#define OP_REG_W_C_BW 0x27
#define OP_NVM_ON_R 0x40
#define OP_NVM_ON_W 0x42
#define OP_NVM_RE 0x44
#define OP_NVM_WE 0x46
#define OP_NVM_OFF 0x48
#define OP_IMG_R 0x50
#define OP_VDM_R 0x60
#define OP_VDM_W 0x62
#define BITS_PER_WORD 8
#define SLOW_BAUD_RATE 12500000
#define DRDY_IRQ_ENABLE 1
#define DRDY_IRQ_DISABLE 0
#define ET5XX_INT_DETECTION_PERIOD 10
#define ET5XX_DETECTION_THRESHOLD 10
#define FP_REGISTER_READ 0x01
#define FP_REGISTER_WRITE 0x02
#define FP_GET_ONE_IMG 0x03
#define FP_SENSOR_RESET 0x04
#define FP_POWER_CONTROL 0x05
#define FP_SET_SPI_CLOCK 0x06
#define FP_RESET_SET 0x07
#define FP_REGISTER_BREAD 0x20
#define FP_REGISTER_BWRITE 0x21
#define FP_REGISTER_MREAD 0x22
#define FP_REGISTER_MWRITE 0x23
#define FP_REGISTER_BREAD_BACKWARD 0x24
#define FP_REGISTER_BWRITE_BACKWARD 0x25
#define FP_VDM_READ 0x30
#define FP_VDM_WRITE 0x31
#define FP_NVM_READ 0X40
#define FP_NVM_WRITE 0x41
#define FP_NVM_OFF 0x42
#define FP_NVM_WRITEEX 0x43
#ifdef ENABLE_SENSORS_FPRINT_SECURE
#define FP_DISABLE_SPI_CLOCK 0x10
#define FP_CPU_SPEEDUP 0x11
#define FP_SET_SENSOR_TYPE 0x14
/* Do not use ioctl number 0x15 */
#define FP_SET_LOCKSCREEN 0x16
#define FP_SET_WAKE_UP_SIGNAL 0x17
#endif
#define FP_POWER_CONTROL_ET5XX 0x18
#define FP_SENSOR_ORIENT 0x19
#define FP_SPI_VALUE 0x1a
#define FP_IOCTL_RESERVED_01 0x1b
#define FP_IOCTL_RESERVED_02 0x1c
/* trigger signal initial routine */
#define INT_TRIGGER_INIT 0xa4
/* trigger signal close routine */
#define INT_TRIGGER_CLOSE 0xa5
/* read trigger status */
#define INT_TRIGGER_READ 0xa6
/* polling trigger status */
#define INT_TRIGGER_POLLING 0xa7
/* polling abort */
#define INT_TRIGGER_ABORT 0xa8
/* Sensor Registers */
#define FDATA_ET5XX_ADDR 0x00
#define FSTATUS_ET5XX_ADDR 0x01
/* Detect Define */
#define FRAME_READY_MASK 0x01
#define SHIFT_BYTE_OF_IMAGE 0
#define DIVISION_OF_IMAGE 4
#define LARGE_SPI_TRANSFER_BUFFER 64
#define MAX_NVM_LEN (32 * 2) /* NVM length in bytes (32 * 16 bits internally) */
#define NVM_WRITE_LENGTH 4096
#define DETECT_ADM 1
struct egis_ioc_transfer {
u8 *tx_buf;
u8 *rx_buf;
__u32 len;
__u32 speed_hz;
__u16 delay_usecs;
__u8 bits_per_word;
__u8 cs_change;
__u8 opcode;
__u8 pad[3];
};
#define EGIS_IOC_MAGIC 'k'
#define EGIS_MSGSIZE(N) \
((((N)*(sizeof(struct egis_ioc_transfer))) < (1 << _IOC_SIZEBITS)) \
? ((N)*(sizeof(struct egis_ioc_transfer))) : 0)
#define EGIS_IOC_MESSAGE(N) _IOW(EGIS_IOC_MAGIC, 0, char[EGIS_MSGSIZE(N)])
struct etspi_data {
dev_t devt;
spinlock_t spi_lock;
struct spi_device *spi;
struct list_head device_entry;
/* buffer is NULL unless this device is open (users > 0) */
struct mutex buf_lock;
unsigned int users;
u8 *buf;/* tx buffer for sensor register read/write */
unsigned int bufsiz; /* MAX size of tx and rx buffer */
unsigned int drdyPin; /* DRDY GPIO pin number */
unsigned int sleepPin; /* Sleep GPIO pin number */
unsigned int ldo_pin; /* Ldo GPIO pin number */
unsigned int min_cpufreq_limit;
unsigned int spi_cs; /* spi cs pin <temporary gpio setting> */
unsigned int drdy_irq_flag; /* irq flag */
bool ldo_onoff;
/* For polling interrupt */
int int_count;
struct timer_list timer;
struct work_struct work_debug;
struct workqueue_struct *wq_dbg;
struct timer_list dbg_timer;
int sensortype;
u32 spi_value;
struct device *fp_device;
int reset_count;
int interrupt_count;
#ifdef ENABLE_SENSORS_FPRINT_SECURE
bool enabled_clk;
bool isGpio_cfgDone;
struct wake_lock fp_spi_lock;
#endif
struct wake_lock fp_signal_lock;
bool tz_mode;
int detect_period;
int detect_threshold;
bool finger_on;
const char *chipid;
unsigned int orient;
struct pinctrl *p;
struct pinctrl_state *pins_sleep;
struct pinctrl_state *pins_idle;
#ifdef NONTZ_BOOST_CONTROL
atomic_t is_boosting;
atomic_t is_sensor_ok;
#endif
struct pm_qos_request pm_qos;
};
int etspi_io_burst_read_register(struct etspi_data *etspi,
struct egis_ioc_transfer *ioc);
int etspi_io_burst_read_register_backward(struct etspi_data *etspi,
struct egis_ioc_transfer *ioc);
int etspi_io_burst_write_register(struct etspi_data *etspi,
struct egis_ioc_transfer *ioc);
int etspi_io_burst_write_register_backward(struct etspi_data *etspi,
struct egis_ioc_transfer *ioc);
int etspi_io_read_register(struct etspi_data *etspi,
u8 *addr, u8 *buf);
int etspi_io_read_registerex(struct etspi_data *etspi,
u8 *addr, u8 *buf, u32 len);
int etspi_io_write_register(struct etspi_data *etspi,
u8 *buf);
int etspi_read_register(struct etspi_data *etspi,
u8 addr, u8 *buf);
int etspi_write_register(struct etspi_data *etspi,
u8 addr, u8 buf);
int etspi_io_nvm_read(struct etspi_data *etspi,
struct egis_ioc_transfer *ioc);
int etspi_io_nvm_write(struct etspi_data *etspi,
struct egis_ioc_transfer *ioc);
int etspi_io_nvm_writeex(struct etspi_data *etspi,
struct egis_ioc_transfer *ioc);
int etspi_io_nvm_off(struct etspi_data *etspi,
struct egis_ioc_transfer *ioc);
int etspi_io_vdm_read(struct etspi_data *etspi,
struct egis_ioc_transfer *ioc);
int etspi_io_vdm_write(struct etspi_data *etspi,
struct egis_ioc_transfer *ioc);
int etspi_io_get_frame(struct etspi_data *etspi, u8 *frame, u32 size);
#endif

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2017 Samsung Electronics. All rights reserved.
*
* 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.
*/
#ifndef FINGERPRINT_H_
#define FINGERPRINT_H_
#include <linux/clk.h>
#include "fingerprint_sysfs.h"
/* fingerprint debug timer */
#define FPSENSOR_DEBUG_TIMER_SEC (10 * HZ)
enum {
DETECT_NORMAL = 0,
DETECT_ADM, /* Always on Detect Mode */
};
/* For Sensor Type Check */
enum {
SENSOR_OOO = -2,
SENSOR_UNKNOWN,
SENSOR_FAILED,
SENSOR_VIPER,
SENSOR_RAPTOR,
SENSOR_EGIS,
SENSOR_VIPER_WOG,
SENSOR_NAMSAN,
SENSOR_GOODIX,
SENSOR_QBT2000,
SENSOR_MAXIMUM,
};
#define SENSOR_STATUS_SIZE 10
static char sensor_status[SENSOR_STATUS_SIZE][10] = {"ooo", "unknown", "failed",
"viper", "raptor", "egis", "viper_wog", "namsan", "goodix", "qbt2000"};
#endif

View File

@@ -0,0 +1,108 @@
/*
* Copyright (C) 2017 Samsung Electronics. All rights reserved.
*
* 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.
*/
/*
* fingerprint sysfs class
*/
#include "fingerprint_sysfs.h"
struct class *fingerprint_class;
EXPORT_SYMBOL_GPL(fingerprint_class);
/*
* Create sysfs interface
*/
void set_fingerprint_attr(struct device *dev,
struct device_attribute *attributes[])
{
int i;
for (i = 0; attributes[i] != NULL; i++)
if ((device_create_file(dev, attributes[i])) < 0)
pr_err("%s: fail device_create_file (dev, attributes[%d])\n", __func__, i);
}
int fingerprint_register(struct device *dev, void *drvdata,
struct device_attribute *attributes[], char *name)
{
int ret = 0;
if (!fingerprint_class) {
fingerprint_class = class_create(THIS_MODULE, "fingerprint");
if (IS_ERR(fingerprint_class))
return PTR_ERR(fingerprint_class);
}
dev = device_create(fingerprint_class, NULL, 0, drvdata, "%s", name);
if (IS_ERR(dev)) {
ret = PTR_ERR(dev);
pr_err("%s: device_create failed! [%d]\n", __func__, ret);
return ret;
}
set_fingerprint_attr(dev, attributes);
return 0;
}
EXPORT_SYMBOL_GPL(fingerprint_register);
void fingerprint_unregister(struct device *dev,
struct device_attribute *attributes[])
{
int i;
for (i = 0; attributes[i] != NULL; i++)
device_remove_file(dev, attributes[i]);
}
EXPORT_SYMBOL_GPL(fingerprint_unregister);
void destroy_fingerprint_class(void)
{
if (fingerprint_class) {
class_destroy(fingerprint_class);
fingerprint_class = NULL;
}
}
EXPORT_SYMBOL_GPL(destroy_fingerprint_class);
static int __init fingerprint_class_init(void)
{
pr_info("%s\n", __func__);
fingerprint_class = class_create(THIS_MODULE, "fingerprint");
if (IS_ERR(fingerprint_class)) {
pr_err("%s, create fingerprint_class is failed.\n",
__func__);
return PTR_ERR(fingerprint_class);
}
fingerprint_class->dev_uevent = NULL;
return 0;
}
static void __exit fingerprint_class_exit(void)
{
if (fingerprint_class) {
class_destroy(fingerprint_class);
fingerprint_class = NULL;
}
}
subsys_initcall(fingerprint_class_init);
module_exit(fingerprint_class_exit);
MODULE_DESCRIPTION("fingerprint sysfs class");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2017, Samsung Electronics Co. Ltd. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
*/
#ifndef FINGERPRINTSYSFS_H_
#define FINGERPRINTSYSFS_H_
#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/err.h>
void set_fingerprint_attr(struct device *dev,
struct device_attribute *attributes[]);
int fingerprint_register(struct device *dev, void *drvdata,
struct device_attribute *attributes[], char *name);
void fingerprint_unregister(struct device *dev,
struct device_attribute *attributes[]);
void destroy_fingerprint_class(void);
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,227 @@
#ifndef __GF_SPI_DRIVER_H
#define __GF_SPI_DRIVER_H
#include <linux/types.h>
#include <linux/netlink.h>
#include <linux/cdev.h>
#ifdef CONFIG_HAS_EARLYSUSPEND
#include <linux/earlysuspend.h>
#else
#include <linux/notifier.h>
#endif
#ifdef ENABLE_SENSORS_FPRINT_SECURE
#if defined (CONFIG_ARCH_EXYNOS9) || defined(CONFIG_ARCH_EXYNOS8)\
|| defined (CONFIG_ARCH_EXYNOS7)
#include <linux/smc.h>
#endif
#include <linux/wakelock.h>
#include <linux/clk.h>
#include <linux/pm_runtime.h>
#include <linux/spi/spidev.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_dma.h>
#include <linux/amba/bus.h>
#include <linux/amba/pl330.h>
#if defined(CONFIG_SECURE_OS_BOOSTER_API)
#if defined(CONFIG_SOC_EXYNOS7870) || defined(CONFIG_SOC_EXYNOS7880)\
|| defined(CONFIG_SOC_EXYNOS7570) || defined(CONFIG_SOC_EXYNOS7885)
#include <soc/samsung/secos_booster.h>
#else
#include <mach/secos_booster.h>
#endif
#elif defined(CONFIG_TZDEV_BOOST)
#include <../drivers/misc/tzdev/tz_boost.h>
#endif
struct sec_spi_info {
int port;
unsigned long speed;
};
#endif
#include <linux/wakelock.h>
#include <linux/cpufreq.h>
#include <linux/pinctrl/consumer.h>
#include "../pinctrl/core.h"
#include <linux/pm_qos.h>
/*
* This feature is temporary for exynos AP only.
* It's for control GPIO config on enabled TZ before enable GPIO protection.
* If it's still defined this feature after enable GPIO protection,
* it will be happened kernel panic
* So it should be un-defined after enable GPIO protection
*/
#undef DISABLED_GPIO_PROTECTION
#define GF_IOC_MAGIC 'g'
#define GF_GW32J_CHIP_ID 0x00220e
#define GF_GW32N_CHIP_ID 0x002215
#define GF_GW36H_CHIP_ID 0x002504
#define GF_GW36C_CHIP_ID 0x002502
#define MAX_BAUD_RATE 4800000
enum gf_netlink_cmd {
GF_NETLINK_TEST = 0,
GF_NETLINK_IRQ = 1,
GF_NETLINK_SCREEN_OFF,
GF_NETLINK_SCREEN_ON
};
struct gf_ioc_transfer {
u8 cmd; /* spi read = 0, spi write = 1 */
u8 reserved;
u16 addr;
u32 len;
u8 *buf;
};
struct gf_ioc_transfer_raw {
u32 len;
u8 *read_buf;
u8 *write_buf;
uint32_t high_time;
uint32_t low_time;
};
#ifdef CONFIG_SENSORS_FINGERPRINT_32BITS_PLATFORM_ONLY
struct gf_ioc_transfer_32 {
u8 cmd; /* spi read = 0, spi write = 1 */
u8 reserved;
u16 addr;
u32 len;
u32 buf;
};
struct gf_ioc_transfer_raw_32 {
u32 len;
u32 read_buf;
u32 write_buf;
uint32_t high_time;
uint32_t low_time;
};
#endif
/* define commands */
#define GF_IOC_INIT _IOR(GF_IOC_MAGIC, 0, u8)
#define GF_IOC_EXIT _IO(GF_IOC_MAGIC, 1)
#define GF_IOC_RESET _IO(GF_IOC_MAGIC, 2)
#define GF_IOC_ENABLE_IRQ _IO(GF_IOC_MAGIC, 3)
#define GF_IOC_DISABLE_IRQ _IO(GF_IOC_MAGIC, 4)
#define GF_IOC_ENABLE_SPI_CLK _IOW(GF_IOC_MAGIC, 5, uint32_t)
#define GF_IOC_DISABLE_SPI_CLK _IO(GF_IOC_MAGIC, 6)
#define GF_IOC_ENABLE_POWER _IO(GF_IOC_MAGIC, 7)
#define GF_IOC_DISABLE_POWER _IO(GF_IOC_MAGIC, 8)
#define GF_IOC_ENTER_SLEEP_MODE _IO(GF_IOC_MAGIC, 10)
#define GF_IOC_GET_FW_INFO _IOR(GF_IOC_MAGIC, 11, u8)
#define GF_IOC_REMOVE _IO(GF_IOC_MAGIC, 12)
/* for SPI REE transfer */
#define GF_IOC_TRANSFER_CMD _IOWR(GF_IOC_MAGIC, 15, \
struct gf_ioc_transfer)
#ifndef CONFIG_SENSORS_FINGERPRINT_32BITS_PLATFORM_ONLY
#define GF_IOC_TRANSFER_RAW_CMD _IOWR(GF_IOC_MAGIC, 16, \
struct gf_ioc_transfer_raw)
#else
#define GF_IOC_TRANSFER_RAW_CMD _IOWR(GF_IOC_MAGIC, 16, \
struct gf_ioc_transfer_raw_32)
#endif
#ifdef ENABLE_SENSORS_FPRINT_SECURE
#define GF_IOC_SET_SENSOR_TYPE _IOW(GF_IOC_MAGIC, 18, unsigned int)
#endif
#define GF_IOC_POWER_CONTROL _IOW(GF_IOC_MAGIC, 19, unsigned int)
#ifdef ENABLE_SENSORS_FPRINT_SECURE
#define GF_IOC_SPEEDUP _IOW(GF_IOC_MAGIC, 20, unsigned int)
#define GF_IOC_SET_LOCKSCREEN _IOW(GF_IOC_MAGIC, 21, unsigned int)
#endif
#define GF_IOC_GET_ORIENT _IOR(GF_IOC_MAGIC, 22, unsigned int)
#define GF_IOC_MAXNR 23 /* THIS MACRO IS NOT USED NOW... */
struct gf_device {
dev_t devno;
struct cdev cdev;
struct device *fp_device;
struct class *class;
struct spi_device *spi;
int device_count;
spinlock_t spi_lock;
struct list_head device_entry;
#ifndef ENABLE_SENSORS_FPRINT_SECURE
u8 *spi_buffer;
#endif
struct mutex buf_lock;
struct mutex release_lock;
struct sock *nl_sk;
u8 buf_status;
#ifdef CONFIG_HAS_EARLYSUSPEND
struct early_suspend early_suspend;
#else
struct notifier_block notifier;
#endif
u8 irq_enabled;
u8 sig_count;
u8 system_status;
u32 pwr_gpio;
u32 reset_gpio;
u32 irq_gpio;
u32 irq;
u8 need_update;
/* for netlink use */
int pid;
struct work_struct work_debug;
struct workqueue_struct *wq_dbg;
struct timer_list dbg_timer;
#ifdef ENABLE_SENSORS_FPRINT_SECURE
bool enabled_clk;
#endif
unsigned int current_spi_speed;
unsigned int orient;
unsigned int min_cpufreq_limit;
int sensortype;
int reset_count;
int interrupt_count;
bool ldo_onoff;
bool tz_mode;
const char *chipid;
struct wake_lock wake_lock;
struct pinctrl *p;
struct pinctrl_state *pins_sleep;
struct pinctrl_state *pins_idle;
struct pm_qos_request pm_qos;
};
int gfspi_get_gpio_dts_info(struct device *dev, struct gf_device *gf_dev);
void gfspi_cleanup_info(struct gf_device *gf_dev);
void gfspi_hw_power_enable(struct gf_device *gf_dev, u8 onoff);
int gfspi_spi_clk_enable(struct gf_device *gf_dev);
int gfspi_spi_clk_disable(struct gf_device *gf_dev);
void gfspi_hw_reset(struct gf_device *gf_dev, u8 delay);
void gfspi_spi_setup_conf(struct gf_device *gf_dev, u32 speed);
int gfspi_pin_control(struct gf_device *gf_dev, bool pin_set);
#ifndef ENABLE_SENSORS_FPRINT_SECURE
int gfspi_spi_read_bytes(struct gf_device *gf_dev, u16 addr,
u32 data_len, u8 *rx_buf);
int gfspi_spi_write_bytes(struct gf_device *gf_dev, u16 addr,
u32 data_len, u8 *tx_buf);
int gfspi_spi_read_byte(struct gf_device *gf_dev, u16 addr, u8 *value);
int gfspi_spi_write_byte(struct gf_device *gf_dev, u16 addr, u8 value);
int gfspi_ioctl_transfer_raw_cmd(struct gf_device *gf_dev,
unsigned long arg, unsigned int bufsiz);
int gfspi_ioctl_spi_init_cfg_cmd(struct gf_device *gf_dev,
unsigned long arg);
#endif /* !ENABLE_SENSORS_FPRINT_SECURE */
#endif /* __GF_SPI_DRIVER_H */

View File

@@ -0,0 +1,470 @@
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/timer.h>
#include <linux/err.h>
#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>
#include "gf_common.h"
void gfspi_spi_setup_conf(struct gf_device *gf_dev, u32 speed)
{
u32 max_speed_hz;
switch (speed) {
case 1:
case 4:
case 6:
case 8:
default:
max_speed_hz = MAX_BAUD_RATE;
break;
}
gf_dev->spi->mode = SPI_MODE_0;
gf_dev->spi->max_speed_hz = max_speed_hz;
gf_dev->spi->bits_per_word = 8;
gf_dev->current_spi_speed = max_speed_hz;
#ifndef ENABLE_SENSORS_FPRINT_SECURE
if (spi_setup(gf_dev->spi))
pr_err("%s, failed to setup spi conf\n", __func__);
#endif
}
#ifndef ENABLE_SENSORS_FPRINT_SECURE
int gfspi_spi_read_bytes(struct gf_device *gf_dev, u16 addr,
u32 data_len, u8 *rx_buf)
{
struct spi_message msg;
struct spi_transfer *xfer = NULL;
u8 *tmp_buf = NULL;
xfer = kzalloc(sizeof(*xfer) * 2, GFP_KERNEL);
if (xfer == NULL)
return -ENOMEM;
tmp_buf = gf_dev->spi_buffer;
spi_message_init(&msg);
*tmp_buf = 0xF0;
*(tmp_buf + 1) = (u8)((addr >> 8) & 0xFF);
*(tmp_buf + 2) = (u8)(addr & 0xFF);
xfer[0].tx_buf = tmp_buf;
xfer[0].len = 3;
xfer[0].delay_usecs = 5;
spi_message_add_tail(&xfer[0], &msg);
spi_sync(gf_dev->spi, &msg);
spi_message_init(&msg);
/* memset((tmp_buf + 4), 0x00, data_len + 1); */
/* 4 bytes align */
*(tmp_buf + 4) = 0xF1;
xfer[1].tx_buf = tmp_buf + 4;
xfer[1].rx_buf = tmp_buf + 4;
xfer[1].len = data_len + 1;
xfer[1].delay_usecs = 5;
spi_message_add_tail(&xfer[1], &msg);
spi_sync(gf_dev->spi, &msg);
memcpy(rx_buf, (tmp_buf + 5), data_len);
kfree(xfer);
if (xfer != NULL)
xfer = NULL;
return 0;
}
int gfspi_spi_write_bytes(struct gf_device *gf_dev, u16 addr,
u32 data_len, u8 *tx_buf)
{
struct spi_message msg;
struct spi_transfer *xfer = NULL;
u8 *tmp_buf = NULL;
xfer = kzalloc(sizeof(*xfer), GFP_KERNEL);
if (xfer == NULL)
return -ENOMEM;
tmp_buf = gf_dev->spi_buffer;
spi_message_init(&msg);
*tmp_buf = 0xF0;
*(tmp_buf + 1) = (u8)((addr >> 8) & 0xFF);
*(tmp_buf + 2) = (u8)(addr & 0xFF);
memcpy(tmp_buf + 3, tx_buf, data_len);
xfer[0].len = data_len + 3;
xfer[0].tx_buf = tmp_buf;
xfer[0].delay_usecs = 5;
spi_message_add_tail(&xfer[0], &msg);
spi_sync(gf_dev->spi, &msg);
kfree(xfer);
if (xfer != NULL)
xfer = NULL;
return 0;
}
int gfspi_spi_read_byte(struct gf_device *gf_dev, u16 addr, u8 *value)
{
struct spi_message msg;
struct spi_transfer *xfer = NULL;
xfer = kzalloc(sizeof(*xfer) * 2, GFP_KERNEL);
if (xfer == NULL)
return -ENOMEM;
spi_message_init(&msg);
*gf_dev->spi_buffer = 0xF0;
*(gf_dev->spi_buffer + 1) = (u8)((addr >> 8) & 0xFF);
*(gf_dev->spi_buffer + 2) = (u8)(addr & 0xFF);
xfer[0].tx_buf = gf_dev->spi_buffer;
xfer[0].len = 3;
xfer[0].delay_usecs = 5;
spi_message_add_tail(&xfer[0], &msg);
spi_sync(gf_dev->spi, &msg);
spi_message_init(&msg);
/* 4 bytes align */
*(gf_dev->spi_buffer + 4) = 0xF1;
xfer[1].tx_buf = gf_dev->spi_buffer + 4;
xfer[1].rx_buf = gf_dev->spi_buffer + 4;
xfer[1].len = 2;
xfer[1].delay_usecs = 5;
spi_message_add_tail(&xfer[1], &msg);
spi_sync(gf_dev->spi, &msg);
*value = *(gf_dev->spi_buffer + 5);
kfree(xfer);
if (xfer != NULL)
xfer = NULL;
return 0;
}
int gfspi_spi_write_byte(struct gf_device *gf_dev, u16 addr, u8 value)
{
struct spi_message msg;
struct spi_transfer *xfer = NULL;
xfer = kzalloc(sizeof(*xfer), GFP_KERNEL);
if (xfer == NULL)
return -ENOMEM;
spi_message_init(&msg);
*gf_dev->spi_buffer = 0xF0;
*(gf_dev->spi_buffer + 1) = (u8)((addr >> 8) & 0xFF);
*(gf_dev->spi_buffer + 2) = (u8)(addr & 0xFF);
*(gf_dev->spi_buffer + 3) = value;
xfer[0].tx_buf = gf_dev->spi_buffer;
xfer[0].len = 3 + 1;
xfer[0].delay_usecs = 5;
spi_message_add_tail(&xfer[0], &msg);
spi_sync(gf_dev->spi, &msg);
kfree(xfer);
if (xfer != NULL)
xfer = NULL;
return 0;
}
static int gfspi_spi_transfer_raw(struct gf_device *gf_dev, u8 *tx_buf,
u8 *rx_buf, u32 len)
{
struct spi_message msg;
struct spi_transfer xfer;
spi_message_init(&msg);
memset(&xfer, 0, sizeof(struct spi_transfer));
xfer.tx_buf = tx_buf;
xfer.rx_buf = rx_buf;
xfer.len = len;
spi_message_add_tail(&xfer, &msg);
spi_sync(gf_dev->spi, &msg);
return 0;
}
int gfspi_ioctl_transfer_raw_cmd(struct gf_device *gf_dev,
unsigned long arg, unsigned int bufsiz)
{
struct gf_ioc_transfer_raw ioc_xraw;
int retval = 0;
#ifdef CONFIG_SENSORS_FINGERPRINT_32BITS_PLATFORM_ONLY
struct gf_ioc_transfer_raw_32 ioc_xraw_32;
u64 read_buf_64;
u64 write_buf_64;
#endif
do {
u8 *tx_buf;
u8 *rx_buf;
uint32_t len;
#ifdef CONFIG_SENSORS_FINGERPRINT_32BITS_PLATFORM_ONLY
if (copy_from_user(&ioc_xraw_32, (void __user *)arg,
sizeof(struct gf_ioc_transfer_raw_32)))
#else
if (copy_from_user(&ioc_xraw, (void __user *)arg,
sizeof(struct gf_ioc_transfer_raw)))
#endif
{
pr_err("%s: Failed to copy gf_ioc_transfer_raw from user to kernel\n",
__func__);
retval = -EFAULT;
break;
}
#ifdef CONFIG_SENSORS_FINGERPRINT_32BITS_PLATFORM_ONLY
read_buf_64 = (u64)ioc_xraw_32.read_buf;
write_buf_64 = (u64)ioc_xraw_32.write_buf;
ioc_xraw.read_buf = (u8 *)read_buf_64;
ioc_xraw.write_buf = (u8 *)write_buf_64;
ioc_xraw.high_time = ioc_xraw_32.high_time;
ioc_xraw.len = ioc_xraw_32.len;
ioc_xraw.low_time = ioc_xraw_32.low_time;
#endif
if ((ioc_xraw.len > bufsiz) || (ioc_xraw.len == 0)) {
pr_err("%s: request transfer length larger than maximum buffer\n",
__func__);
pr_err("%s: buf max %x, buf_len %x\n", __func__, bufsiz, ioc_xraw.len);
retval = -EINVAL;
break;
}
if (ioc_xraw.read_buf == NULL || ioc_xraw.write_buf == NULL) {
pr_err("%s: read buf and write buf can not equal to NULL simultaneously.\n",
__func__);
retval = -EINVAL;
break;
}
/* change speed and set transfer mode */
gfspi_spi_setup_conf(gf_dev, ioc_xraw.high_time);
len = ioc_xraw.len;
tx_buf = kzalloc(len, GFP_KERNEL);
if (tx_buf == NULL) {
pr_err("%s: failed to allocate tx buffer\n",
__func__);
retval = -EMSGSIZE;
break;
}
rx_buf = kzalloc(len, GFP_KERNEL);
if (rx_buf == NULL) {
kfree(tx_buf);
pr_err("%s: failed to allocate rx buffer\n",
__func__);
retval = -EMSGSIZE;
break;
}
if (copy_from_user(tx_buf, (void __user *)ioc_xraw.write_buf,
ioc_xraw.len)) {
kfree(tx_buf);
kfree(rx_buf);
pr_err("Failed to copy gf_ioc_transfer from user to kernel\n");
retval = -EFAULT;
break;
}
gfspi_spi_transfer_raw(gf_dev, tx_buf, rx_buf, len);
if (copy_to_user((void __user *)ioc_xraw.read_buf,
rx_buf, ioc_xraw.len)) {
pr_err("Failed to copy gf_ioc_transfer_raw from kernel to user\n");
retval = -EFAULT;
}
kfree(tx_buf);
kfree(rx_buf);
} while (0);
return retval;
}
int gfspi_ioctl_spi_init_cfg_cmd(struct gf_device *gf_dev, unsigned long arg)
{
return 0;
}
#endif
/*GPIO pins reference.*/
int gfspi_get_gpio_dts_info(struct device *dev, struct gf_device *gf_dev)
{
struct device_node *np = dev->of_node;
int status = 0;
/*get pwr resource*/
gf_dev->pwr_gpio = of_get_named_gpio(np, "goodix,gpio_pwr", 0);
if (!gpio_is_valid(gf_dev->pwr_gpio)) {
pr_err("%s, PWR GPIO is invalid.\n", __func__);
return -1;
}
pr_info("%s, goodix_pwr:%d\n", __func__, gf_dev->pwr_gpio);
status = gpio_request(gf_dev->pwr_gpio, "goodix_pwr");
if (status < 0) {
pr_err("%s, Failed to request PWR GPIO. rc = %d\n",
__func__, status);
return status;
}
gpio_direction_output(gf_dev->pwr_gpio, 0);
/*get reset resource*/
gf_dev->reset_gpio = of_get_named_gpio(np, "goodix,gpio_reset", 0);
if (!gpio_is_valid(gf_dev->reset_gpio)) {
pr_err("%s, RESET GPIO is invalid.\n", __func__);
return -1;
}
pr_info("%s, goodix_reset:%d\n",
__func__, gf_dev->reset_gpio);
status = gpio_request(gf_dev->reset_gpio, "goodix_reset");
if (status < 0) {
pr_err("%s, Failed to request RESET GPIO. rc = %d\n",
__func__, status);
return status;
}
gpio_direction_output(gf_dev->reset_gpio, 0);
/*get irq resourece*/
gf_dev->irq_gpio = of_get_named_gpio(np, "goodix,gpio_irq", 0);
if (!gpio_is_valid(gf_dev->irq_gpio)) {
pr_err("%s, IRQ GPIO is invalid.\n", __func__);
return -1;
}
pr_info("%s, irq_gpio:%d\n", __func__, gf_dev->irq_gpio);
status = gpio_request(gf_dev->irq_gpio, "goodix_irq");
if (status < 0) {
pr_err("%s, Failed to request IRQ GPIO. rc = %d\n",
__func__, status);
return status;
}
gpio_direction_input(gf_dev->irq_gpio);
if (of_property_read_u32(np, "goodix,min_cpufreq_limit",
&gf_dev->min_cpufreq_limit))
gf_dev->min_cpufreq_limit = 0;
if (of_property_read_string_index(np, "goodix,chip_id", 0,
(const char **)&gf_dev->chipid))
gf_dev->chipid = "NULL";
pr_info("%s, Chip ID:%s\n", __func__, gf_dev->chipid);
gf_dev->p = pinctrl_get_select(dev, "default");
if (IS_ERR(gf_dev->p)) {
status = -EINVAL;
pr_err("%s: failed pinctrl_get\n", __func__);
goto fail_pinctrl_get;
}
gf_dev->pins_sleep = pinctrl_lookup_state(gf_dev->p, "sleep");
if (IS_ERR(gf_dev->pins_sleep)) {
pr_err("%s : could not get pins sleep_state (%li)\n",
__func__, PTR_ERR(gf_dev->pins_sleep));
status = -EINVAL;
goto fail_pinctrl_get;
}
gf_dev->pins_idle = pinctrl_lookup_state(gf_dev->p, "idle");
if (IS_ERR(gf_dev->pins_idle)) {
pr_err("%s : could not get pins idle_state (%li)\n",
__func__, PTR_ERR(gf_dev->pins_idle));
goto fail_pinctrl_get;
}
gfspi_pin_control(gf_dev, false);
if (of_property_read_u32(np, "goodix,orient", &gf_dev->orient))
gf_dev->orient = 0;
pr_info("%s: orient=%d\n", __func__, gf_dev->orient);
fail_pinctrl_get:
pinctrl_put(gf_dev->p);
return status;
}
void gfspi_cleanup_info(struct gf_device *gf_dev)
{
if (gpio_is_valid(gf_dev->irq_gpio)) {
gpio_free(gf_dev->irq_gpio);
pr_debug("%s, remove irq_gpio.\n", __func__);
}
if (gpio_is_valid(gf_dev->reset_gpio)) {
gpio_free(gf_dev->reset_gpio);
pr_debug("%s, remove reset_gpio.\n", __func__);
}
if (gpio_is_valid(gf_dev->pwr_gpio)) {
gpio_free(gf_dev->pwr_gpio);
pr_debug("%s, remove pwr_gpio.\n", __func__);
}
}
int gfspi_spi_clk_enable(struct gf_device *gf_dev)
{
int ret_val = 0;
#ifdef ENABLE_SENSORS_FPRINT_SECURE
if (gf_dev->enabled_clk) {
pr_info("%s already enabled same clock.\n",
__func__);
return ret_val;
}
pr_info("%s ENABLE_SPI_CLOCK %ld\n",
__func__, (long int)gf_dev->spi->max_speed_hz);
wake_lock(&gf_dev->wake_lock);
gf_dev->enabled_clk = true;
#endif
return ret_val;
}
int gfspi_spi_clk_disable(struct gf_device *gf_dev)
{
int ret_val = 0;
#ifdef ENABLE_SENSORS_FPRINT_SECURE
if (gf_dev->enabled_clk) {
pr_info("%s DISABLE_SPI_CLOCK\n", __func__);
wake_unlock(&gf_dev->wake_lock);
gf_dev->enabled_clk = false;
pr_info("%s, clk disalbed\n", __func__);
}
#endif
return ret_val;
}
int gfspi_pin_control(struct gf_device *gf_dev, bool pin_set)
{
int status = 0;
if (pin_set) {
if (!IS_ERR(gf_dev->pins_idle)) {
status = pinctrl_select_state(gf_dev->p,
gf_dev->pins_idle);
if (status)
pr_err("%s: can't set pin default state\n",
__func__);
pr_debug("%s idle\n", __func__);
}
} else {
if (!IS_ERR(gf_dev->pins_sleep)) {
status = pinctrl_select_state(gf_dev->p,
gf_dev->pins_sleep);
if (status)
pr_err("%s: can't set pin sleep state\n",
__func__);
pr_debug("%s sleep\n", __func__);
}
}
return status;
}

View File

@@ -28,4 +28,9 @@ config FPGA_MGR_ZYNQ_FPGA
endif # FPGA
config POGO_FPGA
tristate "fpga for pogo keyboard"
help
FPGA manager driver support for pogo fpga.
endmenu

View File

@@ -8,3 +8,4 @@ obj-$(CONFIG_FPGA) += fpga-mgr.o
# FPGA Manager Drivers
obj-$(CONFIG_FPGA_MGR_SOCFPGA) += socfpga.o
obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o
obj-$(CONFIG_POGO_FPGA) += pogo_fpga_dev.o

Some files were not shown because too many files have changed in this diff Show More