/* * * drivers/media/tdmb/tdmb.c * * tdmb driver * * Copyright (C) (2011, 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 version 2. * * This program is distributed "as is" WITHOUT ANY WARRANTY of any * kind, whether express or implied; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #include #include #include #include /* for delay(sleep) */ #include /* for mutex */ #include /*using copy to user */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(CONFIG_TDMB_QUALCOMM) #include #include #endif #if defined(CONFIG_TDMB_NOTIFIER) #include #endif #if defined(CONFIG_TDMB_ANT_DET) static struct wake_lock tdmb_ant_wlock; #endif #define TDMB_WAKE_LOCK_ENABLE #ifdef TDMB_WAKE_LOCK_ENABLE #if defined(CONFIG_TDMB_QUALCOMM) static struct pm_qos_request tdmb_pm_qos_req; #endif static struct wake_lock tdmb_wlock; #endif #include "tdmb.h" #define TDMB_PRE_MALLOC 1 #ifndef VM_RESERVED /* for kernel 3.10 */ #define VM_RESERVED (VM_DONTEXPAND | VM_DONTDUMP) #endif static struct class *tdmb_class; /* ring buffer */ char *ts_ring; unsigned int *tdmb_ts_head; unsigned int *tdmb_ts_tail; char *tdmb_ts_buffer; unsigned int tdmb_ts_size; unsigned int *cmd_head; unsigned int *cmd_tail; static char *cmd_buffer; static unsigned int cmd_size; static unsigned long tdmb_last_ch; static struct tdmb_drv_func *tdmbdrv_func; static struct tdmb_drv_data *tdmbdrv_data; static struct tdmb_dt_platform_data *dt_pdata; static struct device *dmb_device; static bool tdmb_pwr_on; char *tdmb_utcmd_to_str(u32 cmd_type) { switch (cmd_type) { #ifdef TDMB_UNIT_TEST case TDMB_UTCMD_GET_ID: return TDMB_ENUM_STR(TDMB_UTCMD_GET_ID); #endif default: return "unknown"; } } #ifdef CONFIG_TDMB_VREG_SUPPORT static int tdmb_vreg_init(struct device *dev) { int rc = 0; DPRINTK("vdd_name : %s", dt_pdata->tdmb_vreg_name); dt_pdata->tdmb_vreg = regulator_get(dev, dt_pdata->tdmb_vreg_name); if (IS_ERR(dt_pdata->tdmb_vreg)) { DPRINTK("Failed to get tdmb_vreg\n"); rc = -ENXIO; return rc; } rc = regulator_set_voltage(dt_pdata->tdmb_vreg, 1800000, 1800000); if (rc) { DPRINTK("regulator set_vtg failed rc=%d\n", rc); regulator_put(dt_pdata->tdmb_vreg); return rc; } return rc; } static void tdmb_vreg_onoff(bool onoff) { int rc; if (onoff) { if (!regulator_is_enabled(dt_pdata->tdmb_vreg)) { rc = regulator_enable(dt_pdata->tdmb_vreg); if (rc) DPRINTK("tdmb_vreg enable failed rc=%d\n", rc); } } else { if (regulator_is_enabled(dt_pdata->tdmb_vreg)) { rc = regulator_disable(dt_pdata->tdmb_vreg); if (rc) DPRINTK("tdmb_vreg disable failed rc=%d\n", rc); } } DPRINTK("%s : (%d)\n", __func__, onoff); } #endif static void tdmb_set_config_poweron(void) { int rc; if (dt_pdata->tdmb_ldo_en > 0) { rc = gpio_request(dt_pdata->tdmb_ldo_en, "gpio_ldo_en"); if (rc < 0) { DPRINTK("%s: tdmb_ldo_en gpio %d request failed (%d)\n", __func__, dt_pdata->tdmb_ldo_en, rc); } } rc = gpio_request(dt_pdata->tdmb_en, "gpio_tdmb_en"); if (rc < 0) { DPRINTK("%s: tdmb_en gpio %d request failed (%d)\n", __func__, dt_pdata->tdmb_en, rc); } if (dt_pdata->tdmb_lna_en > 0) { dt_pdata->tdmb_lna_en_req = gpio_request(dt_pdata->tdmb_lna_en, "gpio_tdmb_lna_en"); if (dt_pdata->tdmb_lna_en_req < 0) { DPRINTK("%s: tdmb_lna_en gpio %d request failed (%d)\n", __func__, dt_pdata->tdmb_lna_en, dt_pdata->tdmb_lna_en_req); } } if (dt_pdata->tdmb_use_irq) { rc = gpio_request(dt_pdata->tdmb_irq, "gpio_tdmb_irq"); if (rc < 0) { DPRINTK("%s: tdmb_irq gpio %d request failed (%d)\n", __func__, dt_pdata->tdmb_irq, rc); } } if (pinctrl_select_state(dt_pdata->tdmb_pinctrl, dt_pdata->pwr_on)) { DPRINTK("%s: Failed to configure tdmb_on\n", __func__); } } static void tdmb_set_config_poweroff(void) { if (pinctrl_select_state(dt_pdata->tdmb_pinctrl, dt_pdata->pwr_off)) DPRINTK("%s: Failed to configure tdmb_off\n", __func__); gpio_free(dt_pdata->tdmb_en); if (dt_pdata->tdmb_lna_en > 0 && dt_pdata->tdmb_lna_en_req >= 0) { gpio_free(dt_pdata->tdmb_lna_en); } if (dt_pdata->tdmb_use_irq) gpio_free(dt_pdata->tdmb_irq); if (dt_pdata->tdmb_ldo_en > 0) { gpio_free(dt_pdata->tdmb_ldo_en); } } static void tdmb_gpio_on(void) { DPRINTK("%s\n", __func__); #ifdef CONFIG_TDMB_VREG_SUPPORT tdmb_vreg_onoff(true); #endif tdmb_set_config_poweron(); if (dt_pdata->tdmb_ldo_en > 0) { gpio_direction_output(dt_pdata->tdmb_ldo_en, 1); usleep_range(1000, 1200); } gpio_direction_output(dt_pdata->tdmb_en, 0); usleep_range(1000, 1200); gpio_direction_output(dt_pdata->tdmb_en, 1); if (dt_pdata->tdmb_lna_en > 0) gpio_direction_output(dt_pdata->tdmb_lna_en, 1); usleep_range(25000, 26000); if (dt_pdata->tdmb_use_rst) { gpio_direction_output(dt_pdata->tdmb_rst, 0); usleep_range(2000, 2100); gpio_direction_output(dt_pdata->tdmb_rst, 1); usleep_range(10000, 11000); } } static void tdmb_gpio_off(void) { DPRINTK("%s\n", __func__); #ifdef CONFIG_TDMB_VREG_SUPPORT tdmb_vreg_onoff(false); #endif if (dt_pdata->tdmb_ldo_en > 0) gpio_direction_output(dt_pdata->tdmb_ldo_en, 0); gpio_direction_output(dt_pdata->tdmb_en, 0); if (dt_pdata->tdmb_lna_en > 0) gpio_direction_output(dt_pdata->tdmb_lna_en, 0); usleep_range(1000, 1200); if (dt_pdata->tdmb_use_rst) gpio_direction_output(dt_pdata->tdmb_rst, 0); tdmb_set_config_poweroff(); } static bool tdmb_power_on(void) { int param = 0; if (tdmb_create_databuffer(tdmbdrv_func->get_int_size()) == false) { DPRINTK("tdmb_create_databuffer fail\n"); goto create_databuffer_fail; } if (tdmb_create_workqueue() == false) { DPRINTK("tdmb_create_workqueue fail\n"); goto create_workqueue_fail; } #ifdef CONFIG_TDMB_XTAL_FREQ param = dt_pdata->tdmb_xtal_freq; #endif if (tdmbdrv_func->power_on(param) == false) { DPRINTK("power_on fail\n"); goto power_on_fail; } DPRINTK("power_on success\n"); #ifdef TDMB_WAKE_LOCK_ENABLE #if defined(CONFIG_TDMB_QUALCOMM) pm_qos_update_request(&tdmb_pm_qos_req, msm_cpuidle_get_deep_idle_latency()); #endif wake_lock(&tdmb_wlock); #endif tdmb_pwr_on = true; return true; power_on_fail: tdmb_destroy_workqueue(); create_workqueue_fail: tdmb_destroy_databuffer(); create_databuffer_fail: tdmb_pwr_on = false; return false; } static DEFINE_MUTEX(tdmb_lock); static bool tdmb_power_off(void) { DPRINTK("%s : tdmb_pwr_on(%d)\n", __func__, tdmb_pwr_on); if (tdmb_pwr_on) { tdmb_pwr_on = false; tdmbdrv_func->power_off(); tdmb_destroy_workqueue(); tdmb_destroy_databuffer(); #ifdef TDMB_WAKE_LOCK_ENABLE wake_unlock(&tdmb_wlock); #if defined(CONFIG_TDMB_QUALCOMM) pm_qos_update_request(&tdmb_pm_qos_req, PM_QOS_DEFAULT_VALUE); #endif #endif } tdmb_last_ch = 0; return true; } static int tdmb_open(struct inode *inode, struct file *filp) { DPRINTK("%s\n", __func__); return 0; } static ssize_t tdmb_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) { DPRINTK("%s\n", __func__); return 0; } static int tdmb_release(struct inode *inode, struct file *filp) { DPRINTK("%s\n", __func__); mutex_lock(&tdmb_lock); tdmb_power_off(); mutex_unlock(&tdmb_lock); #if TDMB_PRE_MALLOC tdmb_ts_size = 0; cmd_size = 0; #else if (ts_ring != 0) { kfree(ts_ring); ts_ring = 0; tdmb_ts_size = 0; cmd_size = 0; } #endif return 0; } #if TDMB_PRE_MALLOC static void tdmb_make_ring_buffer(void) { size_t size = TDMB_RING_BUFFER_MAPPING_SIZE; /* size should aligned in PAGE_SIZE */ if (size % PAGE_SIZE) /* klaatu hard coding */ size = size + size % PAGE_SIZE; ts_ring = kzalloc(size, GFP_KERNEL); if (!ts_ring) { DPRINTK("RING Buff Create Fail\n"); return; } DPRINTK("RING Buff Create OK\n"); } #endif static int tdmb_mmap(struct file *filp, struct vm_area_struct *vma) { size_t size; unsigned long pfn; DPRINTK("%s\n", __func__); vma->vm_flags |= VM_RESERVED; size = vma->vm_end - vma->vm_start; if (size > TDMB_RING_BUFFER_MAPPING_SIZE) { DPRINTK("over size given : %lx\n", size); return -EAGAIN; } DPRINTK("size given : %lx\n", size); #if TDMB_PRE_MALLOC size = TDMB_RING_BUFFER_MAPPING_SIZE; if (!ts_ring) { DPRINTK("RING Buff ReAlloc(%ld)!!\n", size); #endif /* size should aligned in PAGE_SIZE */ if (size % PAGE_SIZE) /* klaatu hard coding */ size = size + size % PAGE_SIZE; ts_ring = kzalloc(size, GFP_KERNEL); if (!ts_ring) { DPRINTK("RING Buff ReAlloc Fail\n"); return -ENOMEM; } #if TDMB_PRE_MALLOC } #endif pfn = virt_to_phys(ts_ring) >> PAGE_SHIFT; if (remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot)) return -EAGAIN; DPRINTK("succeeded\n"); tdmb_ts_head = (unsigned int *)ts_ring; tdmb_ts_tail = (unsigned int *)(ts_ring + 4); tdmb_ts_buffer = ts_ring + 8; *tdmb_ts_head = 0; *tdmb_ts_tail = 0; tdmb_ts_size = size-8; /* klaatu hard coding */ tdmb_ts_size = ((tdmb_ts_size / DMB_TS_SIZE) * DMB_TS_SIZE) - (30 * DMB_TS_SIZE); DPRINTK("head : %pK, tail : %pK, buffer : %pK, size : %x\n", tdmb_ts_head, tdmb_ts_tail, tdmb_ts_buffer, tdmb_ts_size); cmd_buffer = tdmb_ts_buffer + tdmb_ts_size + 8; cmd_head = (unsigned int *)(cmd_buffer - 8); cmd_tail = (unsigned int *)(cmd_buffer - 4); *cmd_head = 0; *cmd_tail = 0; cmd_size = 30 * DMB_TS_SIZE - 8; /* klaatu hard coding */ DPRINTK("cmd head : %pK, tail : %pK, buffer : %pK, size : %x\n", cmd_head, cmd_tail, cmd_buffer, cmd_size); return 0; } static int _tdmb_cmd_update( unsigned char *cmd_header, unsigned char cmd_header_size, unsigned char *data, unsigned short data_size) { unsigned int size; unsigned int head; unsigned int tail; unsigned int dist; unsigned int temp_size; unsigned int data_size_tmp; if (data_size > cmd_size) { DPRINTK(" Error - cmd size too large\n"); return false; } head = *cmd_head; tail = *cmd_tail; size = cmd_size; data_size_tmp = data_size + cmd_header_size; if (head >= tail) dist = head-tail; else dist = size + head-tail; if (size - dist <= data_size_tmp) { DPRINTK("too small space is left in Cmd Ring Buffer!!\n"); return false; } DPRINTK("%pK head %d tail %d\n", cmd_buffer, head, tail); if (head+data_size_tmp <= size) { memcpy((cmd_buffer + head), (char *)cmd_header, cmd_header_size); memcpy((cmd_buffer + head + cmd_header_size), (char *)data, data_size); head += data_size_tmp; if (head == size) head = 0; } else { temp_size = size - head; if (temp_size < cmd_header_size) { memcpy((cmd_buffer+head), (char *)cmd_header, temp_size); memcpy((cmd_buffer), (char *)cmd_header+temp_size, (cmd_header_size - temp_size)); head = cmd_header_size - temp_size; } else { memcpy((cmd_buffer+head), (char *)cmd_header, cmd_header_size); head += cmd_header_size; if (head == size) head = 0; } temp_size = size - head; if (temp_size < data_size) { memcpy((cmd_buffer+head), (char *)data, temp_size); memcpy((cmd_buffer), (char *)data+temp_size, (data_size - temp_size)); head = data_size - temp_size; } else { memcpy((cmd_buffer+head), (char *)data, data_size); head += data_size; if (head == size) head = 0; } } *cmd_head = head; return true; } unsigned char tdmb_make_result( unsigned char cmd, unsigned short data_len, unsigned char *data) { unsigned char cmd_header[4] = {0,}; cmd_header[0] = TDMB_CMD_START_FLAG; cmd_header[1] = cmd; cmd_header[2] = (data_len>>8)&0xff; cmd_header[3] = data_len&0xff; _tdmb_cmd_update(cmd_header, 4, data, data_len); return true; } unsigned long tdmb_get_chinfo(void) { return tdmb_last_ch; } void tdmb_pull_data(struct work_struct *work) { if (tdmb_pwr_on) tdmbdrv_func->pull_data(); } bool tdmb_control_irq(bool set) { bool ret = true; int irq_ret; if (!dt_pdata->tdmb_use_irq) return false; if (set) { irq_set_irq_type(gpio_to_irq(dt_pdata->tdmb_irq), IRQ_TYPE_EDGE_FALLING); irq_ret = request_irq(gpio_to_irq(dt_pdata->tdmb_irq) , tdmb_irq_handler , 0 , TDMB_DEV_NAME , NULL); if (irq_ret < 0) { DPRINTK("request_irq failed !! \r\n"); ret = false; } } else { free_irq(gpio_to_irq(dt_pdata->tdmb_irq), NULL); } return ret; } void tdmb_control_gpio(bool poweron) { #if defined(CONFIG_TDMB_NOTIFIER) struct tdmb_notifier_struct tdmb_notifier; tdmb_notifier.event = TDMB_NOTIFY_EVENT_TUNNER; tdmb_notifier.tdmb_status.pwr = poweron; tdmb_notifier_call(&tdmb_notifier); #endif if (poweron) tdmb_gpio_on(); else tdmb_gpio_off(); } static long tdmb_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int ret = 0; unsigned long fig_freq = 0; struct ensemble_info_type *ensemble_info; struct tdmb_dm dm_buff; DPRINTK("call %s : 0x%x\n", __func__, cmd); if (_IOC_TYPE(cmd) != IOCTL_MAGIC) { DPRINTK("%s : _IOC_TYPE error\n", __func__); return -EINVAL; } if (_IOC_NR(cmd) >= IOCTL_MAXNR) { DPRINTK("%s : _IOC_NR(cmd) 0x%x\n", __func__, _IOC_NR(cmd)); return -EINVAL; } switch (cmd) { case IOCTL_TDMB_GET_DATA_BUFFSIZE: DPRINTK("IOCTL_TDMB_GET_DATA_BUFFSIZE %d\n", tdmb_ts_size); ret = copy_to_user((unsigned int *)arg, &tdmb_ts_size, sizeof(unsigned int)); break; case IOCTL_TDMB_GET_CMD_BUFFSIZE: DPRINTK("IOCTL_TDMB_GET_CMD_BUFFSIZE %d\n", cmd_size); ret = copy_to_user((unsigned int *)arg, &cmd_size, sizeof(unsigned int)); break; case IOCTL_TDMB_POWER_ON: DPRINTK("IOCTL_TDMB_POWER_ON\n"); mutex_lock(&tdmb_lock); ret = tdmb_power_on(); mutex_unlock(&tdmb_lock); break; case IOCTL_TDMB_POWER_OFF: DPRINTK("IOCTL_TDMB_POWER_OFF\n"); mutex_lock(&tdmb_lock); ret = tdmb_power_off(); mutex_unlock(&tdmb_lock); break; case IOCTL_TDMB_SCAN_FREQ_ASYNC: #if 0 mutex_lock(&tdmb_lock); if (!tdmb_pwr_on) { DPRINTK("IOCTL_TDMB_SCAN_FREQ_ASYNC-Not ready\n"); mutex_unlock(&tdmb_lock); break; } DPRINTK("IOCTL_TDMB_SCAN_FREQ_ASYNC\n"); fig_freq = arg; ensemble_info = vmalloc(sizeof(struct ensemble_info_type)); memset((char *)ensemble_info, 0x00, sizeof(struct ensemble_info_type)); ret = tdmbdrv_func->scan_ch(ensemble_info, fig_freq); if (ret == true) tdmb_make_result(DMB_FIC_RESULT_DONE, sizeof(struct ensemble_info_type), (unsigned char *)ensemble_info); else tdmb_make_result(DMB_FIC_RESULT_FAIL, sizeof(unsigned long), (unsigned char *)&fig_freq); vfree(ensemble_info); tdmb_last_ch = 0; mutex_unlock(&tdmb_lock); #endif DPRINTK("IOCTL_TDMB_SCAN_FREQ_ASYNC - blocked\n"); break; case IOCTL_TDMB_SCAN_FREQ_SYNC: mutex_lock(&tdmb_lock); if (!tdmb_pwr_on) { DPRINTK("IOCTL_TDMB_SCAN_FREQ_SYNC-Not ready\n"); mutex_unlock(&tdmb_lock); break; } ensemble_info = vmalloc(sizeof(struct ensemble_info_type)); if (ensemble_info == NULL) { DPRINTK("IOCTL_TDMB_SCAN_FREQ_SYNC-vmalloc fail\n"); mutex_unlock(&tdmb_lock); break; } if (copy_from_user(ensemble_info, (void *)arg, sizeof(struct ensemble_info_type))) DPRINTK("cmd(%x):copy_from_user failed\n", cmd); else fig_freq = ensemble_info->ensem_freq; DPRINTK("IOCTL_TDMB_SCAN_FREQ_SYNC %ld\n", fig_freq); memset((char *)ensemble_info, 0x00, sizeof(struct ensemble_info_type)); ret = tdmbdrv_func->scan_ch(ensemble_info, fig_freq); if (ret == true) { if (copy_to_user((struct ensemble_info_type *)arg, ensemble_info, sizeof(struct ensemble_info_type)) ) DPRINTK("cmd(%x):copy_to_user failed\n", cmd); } vfree(ensemble_info); tdmb_last_ch = 0; mutex_unlock(&tdmb_lock); break; case IOCTL_TDMB_SCANSTOP: DPRINTK("IOCTL_TDMB_SCANSTOP\n"); ret = false; break; case IOCTL_TDMB_ASSIGN_CH: mutex_lock(&tdmb_lock); if (!tdmb_pwr_on) { DPRINTK("IOCTL_TDMB_ASSIGN_CH-Not ready\n"); mutex_unlock(&tdmb_lock); break; } DPRINTK("IOCTL_TDMB_ASSIGN_CH %ld\n", arg); tdmb_init_data(); ret = tdmbdrv_func->set_ch(arg, (arg % 1000), false); if (ret == true) tdmb_last_ch = arg; else tdmb_last_ch = 0; mutex_unlock(&tdmb_lock); break; case IOCTL_TDMB_ASSIGN_CH_TEST: mutex_lock(&tdmb_lock); if (!tdmb_pwr_on) { DPRINTK("IOCTL_TDMB_ASSIGN_CH_TEST-Not ready\n"); mutex_unlock(&tdmb_lock); break; } DPRINTK("IOCTL_TDMB_ASSIGN_CH_TEST %ld\n", arg); tdmb_init_data(); ret = tdmbdrv_func->set_ch(arg, (arg % 1000), true); if (ret == true) tdmb_last_ch = arg; else tdmb_last_ch = 0; mutex_unlock(&tdmb_lock); break; case IOCTL_TDMB_GET_DM: mutex_lock(&tdmb_lock); if (!tdmb_pwr_on) { DPRINTK("IOCTL_TDMB_GET_DM-Not ready\n"); mutex_unlock(&tdmb_lock); break; } tdmbdrv_func->get_dm(&dm_buff); if (copy_to_user((struct tdmb_dm *)arg, &dm_buff, sizeof(struct tdmb_dm))) DPRINTK("IOCTL_TDMB_GET_DM : copy_to_user failed\n"); ret = true; DPRINTK("rssi %d, ber %d, ANT %d\n", dm_buff.rssi, dm_buff.ber, dm_buff.antenna); mutex_unlock(&tdmb_lock); break; case IOCTL_TDMB_SET_AUTOSTART: DPRINTK("IOCTL_TDMB_SET_AUTOSTART : %ld\n", arg); #if defined(CONFIG_TDMB_ANT_DET) tdmb_ant_det_irq_set(arg); #endif break; } return ret; } #ifdef CONFIG_COMPAT static long tdmb_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { DPRINTK("call %s : 0x%x\n", __func__, cmd); arg = (unsigned long) compat_ptr(arg); switch (cmd) { case IOCTL_TDMB_GET_DATA_BUFFSIZE: case IOCTL_TDMB_GET_CMD_BUFFSIZE: case IOCTL_TDMB_POWER_ON: case IOCTL_TDMB_POWER_OFF: case IOCTL_TDMB_SCAN_FREQ_ASYNC: case IOCTL_TDMB_SCAN_FREQ_SYNC: case IOCTL_TDMB_SCANSTOP: case IOCTL_TDMB_ASSIGN_CH: case IOCTL_TDMB_ASSIGN_CH_TEST: case IOCTL_TDMB_GET_DM: case IOCTL_TDMB_SET_AUTOSTART: return tdmb_ioctl(filp, cmd, arg); } return -ENOIOCTLCMD; } #endif static const struct file_operations tdmb_ctl_fops = { .owner = THIS_MODULE, .open = tdmb_open, .read = tdmb_read, .unlocked_ioctl = tdmb_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = tdmb_compat_ioctl, #endif .mmap = tdmb_mmap, .release = tdmb_release, .llseek = no_llseek, }; static struct tdmb_drv_func *tdmb_get_drv_func(void) { struct tdmb_drv_func * (*func)(void); #if defined(CONFIG_TDMB_FC8050) func = fc8050_drv_func; #elif defined(CONFIG_TDMB_FC8080) func = fc8080_drv_func; #elif defined(CONFIG_TDMB_MTV318) func = mtv318_drv_func; #elif defined(CONFIG_TDMB_MTV319) func = mtv319_drv_func; #elif defined(CONFIG_TDMB_TCC3170) func = tcc3170_drv_func; #else #error what??? #endif return func(); } #if defined(CONFIG_TDMB_ANT_DET) enum { TDMB_ANT_OPEN = 0, TDMB_ANT_CLOSE, TDMB_ANT_UNKNOWN, }; enum { TDMB_ANT_DET_LOW = 0, TDMB_ANT_DET_HIGH, }; static struct input_dev *tdmb_ant_input; static int tdmb_check_ant; static int ant_prev_status; static int ant_irq_ret = -1; #define TDMB_ANT_WAIT_INIT_TIME 500000 /* us */ #define TDMB_ANT_CHECK_DURATION 50000 /* us */ #define TDMB_ANT_CHECK_COUNT 10 #define TDMB_ANT_WLOCK_TIMEOUT \ ((TDMB_ANT_CHECK_DURATION * TDMB_ANT_CHECK_COUNT * 2) / 500000) static int tdmb_ant_det_check_value(void) { int loop = 0, cur_val = 0; int ret = TDMB_ANT_UNKNOWN; tdmb_check_ant = 1; DPRINTK("%s ant_prev_status(%d)\n", __func__, ant_prev_status); usleep_range(TDMB_ANT_WAIT_INIT_TIME, TDMB_ANT_WAIT_INIT_TIME + 1000); /* wait initial noise */ for (loop = 0; loop < TDMB_ANT_CHECK_COUNT; loop++) { usleep_range(TDMB_ANT_CHECK_DURATION, TDMB_ANT_CHECK_DURATION + 1000); cur_val = gpio_get_value_cansleep(dt_pdata->tdmb_ant_irq); if (ant_prev_status == cur_val) break; } if (loop == TDMB_ANT_CHECK_COUNT) { if (ant_prev_status == TDMB_ANT_DET_LOW && cur_val == TDMB_ANT_DET_HIGH) { ret = TDMB_ANT_OPEN; } else if (ant_prev_status == TDMB_ANT_DET_HIGH && cur_val == TDMB_ANT_DET_LOW) { ret = TDMB_ANT_CLOSE; } ant_prev_status = cur_val; } tdmb_check_ant = 0; DPRINTK("%s cnt(%d) cur(%d) prev(%d)\n", __func__, loop, cur_val, ant_prev_status); return ret; } static int tdmb_ant_det_ignore_irq(void) { DPRINTK("chk_ant=%d\n", tdmb_check_ant); return tdmb_check_ant; } static void tdmb_ant_det_work_func(struct work_struct *work) { if (!tdmb_ant_input) { DPRINTK("%s: input device is not registered\n", __func__); return; } switch (tdmb_ant_det_check_value()) { case TDMB_ANT_OPEN: input_report_key(tdmb_ant_input, KEY_DMB_ANT_DET_UP, 1); input_report_key(tdmb_ant_input, KEY_DMB_ANT_DET_UP, 0); input_sync(tdmb_ant_input); DPRINTK("%s : TDMB_ANT_OPEN\n", __func__); break; case TDMB_ANT_CLOSE: input_report_key(tdmb_ant_input, KEY_DMB_ANT_DET_DOWN, 1); input_report_key(tdmb_ant_input, KEY_DMB_ANT_DET_DOWN, 0); input_sync(tdmb_ant_input); DPRINTK("%s : TDMB_ANT_CLOSE\n", __func__); break; case TDMB_ANT_UNKNOWN: DPRINTK("%s : TDMB_ANT_UNKNOWN\n", __func__); break; default: break; } } static struct workqueue_struct *tdmb_ant_det_wq; static DECLARE_WORK(tdmb_ant_det_work, tdmb_ant_det_work_func); static bool tdmb_ant_det_reg_input(struct platform_device *pdev) { struct input_dev *input; int err; DPRINTK("%s\n", __func__); input = input_allocate_device(); if (!input) { DPRINTK("Can't allocate input device\n"); err = -ENOMEM; } set_bit(EV_KEY, input->evbit); set_bit(KEY_DMB_ANT_DET_UP & KEY_MAX, input->keybit); set_bit(KEY_DMB_ANT_DET_DOWN & KEY_MAX, input->keybit); input->name = "sec_dmb_key"; input->phys = "sec_dmb_key/input0"; input->dev.parent = &pdev->dev; err = input_register_device(input); if (err) { DPRINTK("Can't register dmb_ant_det key: %d\n", err); goto free_input_dev; } tdmb_ant_input = input; return true; free_input_dev: input_free_device(input); return false; } static void tdmb_ant_det_unreg_input(void) { DPRINTK("%s\n", __func__); if (tdmb_ant_input) { input_unregister_device(tdmb_ant_input); tdmb_ant_input = NULL; } } static bool tdmb_ant_det_create_wq(void) { DPRINTK("%s\n", __func__); tdmb_ant_det_wq = create_singlethread_workqueue("tdmb_ant_det_wq"); if (tdmb_ant_det_wq) return true; else return false; } static bool tdmb_ant_det_destroy_wq(void) { DPRINTK("%s\n", __func__); if (tdmb_ant_det_wq) { flush_workqueue(tdmb_ant_det_wq); destroy_workqueue(tdmb_ant_det_wq); tdmb_ant_det_wq = NULL; } return true; } static irqreturn_t tdmb_ant_det_irq_handler(int irq, void *dev_id) { int ret = 0; if (tdmb_ant_det_ignore_irq()) return IRQ_HANDLED; wake_lock_timeout(&tdmb_ant_wlock, TDMB_ANT_WLOCK_TIMEOUT * HZ); if (tdmb_ant_det_wq) { ret = queue_work(tdmb_ant_det_wq, &tdmb_ant_det_work); if (ret == 0) DPRINTK("%s queue_work fail\n", __func__); } return IRQ_HANDLED; } bool tdmb_ant_det_irq_set(bool set) { bool ret = true; DPRINTK("%s : set(%d) ant_irq(%d)\n", __func__, set, ant_irq_ret); if (set) { if (ant_irq_ret < 0) { ant_prev_status = gpio_get_value_cansleep(dt_pdata->tdmb_ant_irq); irq_set_irq_type(gpio_to_irq(dt_pdata->tdmb_ant_irq) , IRQ_TYPE_EDGE_BOTH); ant_irq_ret = request_irq(gpio_to_irq(dt_pdata->tdmb_ant_irq) , tdmb_ant_det_irq_handler , IRQF_DISABLED , "tdmb_ant_det" , NULL); if (ant_irq_ret < 0) { DPRINTK("%s %d\r\n", __func__, ant_irq_ret); ret = false; } else { enable_irq_wake(gpio_to_irq(dt_pdata->tdmb_ant_irq)); } } } else { if (ant_irq_ret >= 0) { disable_irq_wake(gpio_to_irq(dt_pdata->tdmb_ant_irq)); free_irq(gpio_to_irq(dt_pdata->tdmb_ant_irq), NULL); ant_irq_ret = -1; ret = false; } } return ret; } #endif static struct tdmb_dt_platform_data *get_tdmb_dt_pdata(struct device *dev) { struct tdmb_dt_platform_data *pdata; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) { DPRINTK("%s : could not allocate memory for platform data\n", __func__); goto err; } pdata->tdmb_en = of_get_named_gpio(dev->of_node, "tdmb_pwr_en", 0); if (!gpio_is_valid(pdata->tdmb_en)) { DPRINTK("Failed to get is valid tdmb_en\n"); goto alloc_err; } pdata->tdmb_ldo_en = of_get_named_gpio(dev->of_node, "tdmb_ldo_en", 0); if (!gpio_is_valid(pdata->tdmb_ldo_en)) { DPRINTK("Failed to get is valid tdmb_ldo_en\n"); } pdata->tdmb_lna_en = of_get_named_gpio(dev->of_node, "tdmb_lna_en", 0); if (!gpio_is_valid(pdata->tdmb_lna_en)) { DPRINTK("Failed to get is valid tdmb_lna_en\n"); } pdata->tdmb_use_rst = of_property_read_bool(dev->of_node, "tdmb_use_rst"); if (pdata->tdmb_use_rst) { pdata->tdmb_rst = of_get_named_gpio(dev->of_node, "tdmb_rst", 0); if (!gpio_is_valid(pdata->tdmb_rst)) { DPRINTK("Failed to get is valid tdmb_rst\n"); goto alloc_err; } } else { DPRINTK("%s : without tdmb_use_rst\n", __func__); } pdata->tdmb_use_irq = of_property_read_bool(dev->of_node, "tdmb_use_irq"); if (pdata->tdmb_use_irq) { pdata->tdmb_irq = of_get_named_gpio(dev->of_node, "tdmb_irq", 0); if (!gpio_is_valid(pdata->tdmb_irq)) { DPRINTK("Failed to get is valid tdmb_irq\n"); goto alloc_err; } } else { DPRINTK("%s : without tdmb_use_irq\n", __func__); } #ifdef CONFIG_TDMB_XTAL_FREQ if (of_property_read_u32(dev->of_node, "tdmb_xtal_freq", &pdata->tdmb_xtal_freq)) { DPRINTK("Failed to get tdmb_xtal_freq\n"); goto alloc_err; } #endif pdata->tdmb_pinctrl = devm_pinctrl_get(dev); if (IS_ERR(pdata->tdmb_pinctrl)) { DPRINTK("devm_pinctrl_get is fail"); goto alloc_err; } pdata->pwr_on = pinctrl_lookup_state(pdata->tdmb_pinctrl, "tdmb_on"); if (IS_ERR(pdata->pwr_on)) { DPRINTK("%s : could not get pins tdmb_pwr_on state (%li)\n", __func__, PTR_ERR(pdata->pwr_on)); goto err_pinctrl_lookup_state; } pdata->pwr_off = pinctrl_lookup_state(pdata->tdmb_pinctrl, "tdmb_off"); if (IS_ERR(pdata->pwr_off)) { DPRINTK("%s : could not get pins tdmb_pwr_off state (%li)\n", __func__, PTR_ERR(pdata->pwr_off)); goto err_pinctrl_lookup_state; } #ifdef CONFIG_TDMB_ANT_DET pdata->tdmb_ant_irq = of_get_named_gpio(dev->of_node, "tdmb_ant_irq", 0); if (!gpio_is_valid(pdata->tdmb_ant_irq)) { DPRINTK("Failed to get is valid tdmb_ant_irq\n"); goto alloc_err; } #endif #ifdef CONFIG_TDMB_VREG_SUPPORT if (of_property_read_string(dev->of_node, "tdmb_vreg_supply", &pdata->tdmb_vreg_name)) { DPRINTK("Unable to find tdmb_vdd_supply\n"); goto alloc_err; } #endif return pdata; err_pinctrl_lookup_state: devm_pinctrl_put(pdata->tdmb_pinctrl); alloc_err: devm_kfree(dev, pdata); err: return NULL; } #ifdef TDMB_UNIT_TEST static bool tdmb_unit_test_get_id(void) { bool ret = false; DPRINTK("%s: +++\n", __func__); //.TODO: return ret; } static ssize_t tdmb_unit_test_show(struct class *class, struct class_attribute *attr, char *buf) { struct tdmb_drv_data *pdata = tdmbdrv_data; int rc, cmd = pdata->test_cmd; bool res = false; DPRINTK("%s: test_cmd: %s\n", __func__, tdmb_utcmd_to_str(cmd)); switch (cmd) { case TDMB_UTCMD_GET_ID: res = tdmb_unit_test_get_id(); break; default: DPRINTK("%s: invalid test_cmd: %d\n", __func__, cmd); break; } rc = scnprintf(buf, 3, "%d\n", res ? 1 : 0); return rc; } static ssize_t tdmb_unit_test_store(struct class *dev, struct class_attribute *attr, const char *buf, size_t size) { struct tdmb_drv_data *pdata = tdmbdrv_data; int val[10] = {0, }; get_options(buf, 10, val); pdata->test_cmd = val[1]; DPRINTK("%s: test_cmd: %d...%s\n", __func__, pdata->test_cmd, tdmb_utcmd_to_str(pdata->test_cmd)); return size; } static CLASS_ATTR_RW(tdmb_unit_test); #endif static int tdmb_probe(struct platform_device *pdev) { int ret; struct device *tdmb_dev; struct tdmb_drv_data *pdata; DPRINTK("call %s\n", __func__); #if defined(CONFIG_TDMB_TSIF_QC) tdmb_tsi_init(); #endif pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) { DPRINTK("%s : Fail to allocate memory for drv data\n", __func__); return -ENOMEM; } dt_pdata = get_tdmb_dt_pdata(&pdev->dev); if (!dt_pdata) { pr_err("%s : tdmb_dt_pdata is NULL.\n", __func__); return -ENODEV; } if (pinctrl_select_state(dt_pdata->tdmb_pinctrl, dt_pdata->pwr_off)) DPRINTK("%s: Failed to configure tdmb_gpio_init\n", __func__); dmb_device = &pdev->dev; ret = register_chrdev(TDMB_DEV_MAJOR, TDMB_DEV_NAME, &tdmb_ctl_fops); if (ret < 0) DPRINTK("register_chrdev(TDMB_DEV) failed!\n"); tdmb_class = class_create(THIS_MODULE, TDMB_DEV_NAME); if (IS_ERR(tdmb_class)) { unregister_chrdev(TDMB_DEV_MAJOR, TDMB_DEV_NAME); DPRINTK("class_create failed!\n"); return -EFAULT; } #ifdef TDMB_UNIT_TEST ret = class_create_file(tdmb_class, &class_attr_tdmb_unit_test); if (ret) pr_err("failed to create attr_unit_test(%d)\n", ret); #endif tdmb_dev = device_create(tdmb_class, NULL, MKDEV(TDMB_DEV_MAJOR, TDMB_DEV_MINOR), NULL, TDMB_DEV_NAME); if (IS_ERR(tdmb_dev)) { DPRINTK("device_create failed!\n"); unregister_chrdev(TDMB_DEV_MAJOR, TDMB_DEV_NAME); class_destroy(tdmb_class); return -EFAULT; } tdmbdrv_func = tdmb_get_drv_func(); if (tdmbdrv_func->init) tdmbdrv_func->init(); #if TDMB_PRE_MALLOC tdmb_make_ring_buffer(); #endif #ifdef TDMB_WAKE_LOCK_ENABLE #if defined(CONFIG_TDMB_QUALCOMM) pm_qos_add_request(&tdmb_pm_qos_req, PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE); #endif wake_lock_init(&tdmb_wlock, WAKE_LOCK_SUSPEND, "tdmb_wlock"); #endif #ifdef CONFIG_TDMB_VREG_SUPPORT ret = tdmb_vreg_init(&pdev->dev); if (ret) { DPRINTK("tdmb_vreg_init failed!\n"); return -ENXIO; } #endif #if defined(CONFIG_TDMB_ANT_DET) wake_lock_init(&tdmb_ant_wlock, WAKE_LOCK_SUSPEND, "tdmb_ant_wlock"); if (!tdmb_ant_det_reg_input(pdev)) goto err_reg_input; if (!tdmb_ant_det_create_wq()) goto free_reg_input; return 0; free_reg_input: tdmb_ant_det_unreg_input(); err_reg_input: return -EFAULT; #endif tdmbdrv_data = pdata; return 0; } static int tdmb_remove(struct platform_device *pdev) { DPRINTK("%s!\n", __func__); #if defined(CONFIG_TDMB_ANT_DET) tdmb_ant_det_unreg_input(); tdmb_ant_det_destroy_wq(); tdmb_ant_det_irq_set(false); wake_lock_destroy(&tdmb_ant_wlock); #endif #if defined(CONFIG_TDMB_TSIF_QC) tdmb_tsi_deinit(); #endif #ifdef CONFIG_TDMB_VREG_SUPPORT regulator_put(dt_pdata->tdmb_vreg); #endif if (!tdmbdrv_data) { devm_kfree(&pdev->dev, tdmbdrv_data); tdmbdrv_data = NULL; } return 0; } static int tdmb_suspend(struct platform_device *pdev, pm_message_t mesg) { return 0; } static int tdmb_resume(struct platform_device *pdev) { return 0; } static const struct of_device_id tdmb_match_table[] = { {.compatible = "samsung,tdmb"}, {} }; static struct platform_driver tdmb_driver = { .probe = tdmb_probe, .remove = tdmb_remove, .suspend = tdmb_suspend, .resume = tdmb_resume, .driver = { .owner = THIS_MODULE, .name = "tdmb", .of_match_table = tdmb_match_table, }, }; static int __init tdmb_init(void) { int ret; DPRINTK(" module init\n"); ret = platform_driver_register(&tdmb_driver); if (ret) return ret; return 0; } static void __exit tdmb_exit(void) { DPRINTK(" module exit\n"); #if TDMB_PRE_MALLOC if (ts_ring != 0) { kfree(ts_ring); ts_ring = 0; } #endif unregister_chrdev(TDMB_DEV_MAJOR, "tdmb"); device_destroy(tdmb_class, MKDEV(TDMB_DEV_MAJOR, TDMB_DEV_MINOR)); class_destroy(tdmb_class); platform_driver_unregister(&tdmb_driver); #ifdef TDMB_WAKE_LOCK_ENABLE #if defined(CONFIG_TDMB_QUALCOMM) pm_qos_remove_request(&tdmb_pm_qos_req); #endif wake_lock_destroy(&tdmb_wlock); #endif } module_init(tdmb_init); module_exit(tdmb_exit); MODULE_AUTHOR("Samsung"); MODULE_DESCRIPTION("TDMB Driver"); MODULE_LICENSE("GPL v2");