/* * drivers/media/radio/s610/radio-s610.c -- V4L2 driver for S610 chips * * 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 of the License. * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_SCSC_FM #include #endif #include "fm_low_struc.h" #include "radio-s610.h" #include "../../../../sound/soc/samsung/abox/abox.h" static int radio_region; module_param(radio_region, int, 0); MODULE_PARM_DESC(radio_region, "Region: 0=Europe/US, 1=Japan"); struct s610_radio; /* global variable for radio structure */ struct s610_radio *gradio; #define FAC_VALUE 16000 static int s610_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl); static int s610_radio_s_ctrl(struct v4l2_ctrl *ctrl); static int s610_core_set_power_state(struct s610_radio *radio, u8 next_state); int fm_set_mute_mode(struct s610_radio *radio, u8 mute_mode_toset); static int fm_radio_runtime_resume(struct device *dev); void enable_FM_mux_clk_aud(struct s610_radio *radio); void disable_clk_gating(struct s610_radio *radio); static int fm_clk_get(struct s610_radio *radio); static int fm_clk_prepare(struct s610_radio *radio); static int fm_clk_enable(struct s610_radio *radio); static void fm_clk_unprepare(struct s610_radio *radio); static void fm_clk_disable(struct s610_radio *radio); static void fm_clk_put(struct s610_radio *radio); signed int exynos_get_fm_open_status(void); signed int shared_fm_open_cnt; extern fm_conf_ini_values low_fm_conf_init; u32 *fm_spur_init; u32 *fm_spur_trf_init; u32 *fm_dual_clk_init; u32 vol_level_init[FM_RX_VOLUME_GAIN_STEP] = { 0, 16, 23, 32, 45, 64, 90, 128, 181, 256, 362, 512, 724, 1024, 1447, 2047 }; static const struct v4l2_ctrl_ops s610_ctrl_ops = { .s_ctrl = s610_radio_s_ctrl, .g_volatile_ctrl = s610_radio_g_volatile_ctrl, }; enum s610_ctrl_idx { S610_IDX_CH_SPACING = 0x01, S610_IDX_CH_BAND = 0x02, S610_IDX_SOFT_STEREO_BLEND = 0x03, S610_IDX_SOFT_STEREO_BLEND_COEFF = 0x04, S610_IDX_SOFT_MUTE_COEFF = 0x05, S610_IDX_RSSI_CURR = 0x06, S610_IDX_SNR_CURR = 0x07, S610_IDX_SEEK_CANCEL = 0x08, S610_IDX_SEEK_MODE = 0x09, S610_IDX_RDS_ON = 0x0A, S610_IDX_IF_COUNT1 = 0x0B, S610_IDX_IF_COUNT2 = 0x0C, S610_IDX_RSSI_TH = 0x0D, S610_IDX_KERNEL_VER = 0x0E, S610_IDX_SOFT_STEREO_BLEND_REF = 0x0F }; static struct v4l2_ctrl_config s610_ctrls[] = { /** * S610 during its station seeking(or tuning) process uses several * parameters to detrmine if "the station" is valid: * * - Signal's RSSI(in dBuV) must be greater than * #V4L2_CID_S610_RSSI_THRESHOLD */ [S610_IDX_CH_SPACING] = {/*0x01*/ .ops = &s610_ctrl_ops, .id = V4L2_CID_S610_CH_SPACING, .type = V4L2_CTRL_TYPE_INTEGER, .name = "Channel Spacing", .min = 0, .max = 2, .step = 1, }, [S610_IDX_CH_BAND] = { /*0x02*/ .ops = &s610_ctrl_ops, .id = V4L2_CID_S610_CH_BAND, .type = V4L2_CTRL_TYPE_INTEGER, .name = "Channel Band", .min = 0, .max = 1, .step = 1, }, [S610_IDX_SOFT_STEREO_BLEND] = { /*0x03*/ .ops = &s610_ctrl_ops, .id = V4L2_CID_S610_SOFT_STEREO_BLEND, .type = V4L2_CTRL_TYPE_INTEGER, .name = "Soft Stereo Blend", .min = 0, .max = 1, .step = 1, }, [S610_IDX_SOFT_STEREO_BLEND_COEFF] = { /*0x04*/ .ops = &s610_ctrl_ops, .id = V4L2_CID_S610_SOFT_STEREO_BLEND_COEFF, .type = V4L2_CTRL_TYPE_INTEGER, .name = "Soft Stereo Blend COEFF", .min = 0, .max = 0xffff, .step = 1, }, [S610_IDX_SOFT_MUTE_COEFF] = { /*0x05*/ .ops = &s610_ctrl_ops, .id = V4L2_CID_S610_SOFT_MUTE_COEFF, .type = V4L2_CTRL_TYPE_INTEGER, .name = "Soft Mute COEFF Set", .min = 0, .max = 0xffff, .step = 1, }, [S610_IDX_RSSI_CURR] = { /*0x06*/ .ops = &s610_ctrl_ops, .id = V4L2_CID_S610_RSSI_CURR, .type = V4L2_CTRL_TYPE_INTEGER, .name = "RSSI Current", .min = 0, .max = 0xffff, .step = 1, }, [S610_IDX_SNR_CURR] = { /*0x07*/ .ops = &s610_ctrl_ops, .id = V4L2_CID_S610_SNR_CURR, .type = V4L2_CTRL_TYPE_INTEGER, .name = "SNR Current", .min = 0, .max = 0xffff, .step = 1, }, [S610_IDX_SEEK_CANCEL] = { /*0x08*/ .ops = &s610_ctrl_ops, .id = V4L2_CID_S610_SEEK_CANCEL, .type = V4L2_CTRL_TYPE_INTEGER, .name = "Seek Cancel", .min = 0, .max = 1, .step = 1, }, [S610_IDX_SEEK_MODE] = { /*0x09*/ .ops = &s610_ctrl_ops, .id = V4L2_CID_S610_SEEK_MODE, .type = V4L2_CTRL_TYPE_INTEGER, .name = "Seek Mode", .min = 0, .max = 4, .step = 1, }, [S610_IDX_RDS_ON] = { /*0x0A*/ .ops = &s610_ctrl_ops, .id = V4L2_CID_S610_RDS_ON, .type = V4L2_CTRL_TYPE_INTEGER, .name = "RDS ON", .min = 0, .max = 0x0F, .step = 1, }, [S610_IDX_IF_COUNT1] = { /*0x0B*/ .ops = &s610_ctrl_ops, .id = V4L2_CID_S610_IF_COUNT1, .type = V4L2_CTRL_TYPE_INTEGER, .name = "IF_COUNT1", .min = 0, .max = 0xffff, .step = 1, }, [S610_IDX_IF_COUNT2] = { /*0x0C*/ .ops = &s610_ctrl_ops, .id = V4L2_CID_S610_IF_COUNT2, .type = V4L2_CTRL_TYPE_INTEGER, .name = "IF_COUNT2", .min = 0, .max = 0xffff, .step = 1, }, [S610_IDX_RSSI_TH] = { /*0x0D*/ .ops = &s610_ctrl_ops, .id = V4L2_CID_S610_RSSI_TH, .type = V4L2_CTRL_TYPE_INTEGER, .name = "RSSI Th", .min = 0, .max = 0xffff, .step = 1, }, [S610_IDX_KERNEL_VER] = { /*0x0E*/ .ops = &s610_ctrl_ops, .id = V4L2_CID_S610_KERNEL_VER, .type = V4L2_CTRL_TYPE_INTEGER, .name = "KERNEL_VER", .min = 0, .max = 0xffff, .step = 1, }, [S610_IDX_SOFT_STEREO_BLEND_REF] = { /*0x0F*/ .ops = &s610_ctrl_ops, .id = V4L2_CID_S610_SOFT_STEREO_BLEND_REF, .type = V4L2_CTRL_TYPE_INTEGER, .name = "Soft Stereo Blend Ref", .min = 0, .max = 0xffff, .step = 1, }, }; static const struct v4l2_frequency_band s610_bands[] = { [0] = { .type = V4L2_TUNER_RADIO, .index = S610_BAND_FM, .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | V4L2_TUNER_CAP_FREQ_BANDS, /* default region Eu/US */ .rangelow = 87500*FAC_VALUE, .rangehigh = 108000*FAC_VALUE, .modulation = V4L2_BAND_MODULATION_FM, }, [1] = { .type = V4L2_TUNER_RADIO, .index = S610_BAND_FM, .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | V4L2_TUNER_CAP_FREQ_BANDS, /* default region Eu/US */ .rangelow = 76000*FAC_VALUE, .rangehigh = 90000*FAC_VALUE, .modulation = V4L2_BAND_MODULATION_FM, }, }; /* Region info */ static struct region_info region_configs[] = { /* Europe/US */ { .chanl_space = FM_CHANNEL_SPACING_200KHZ * FM_FREQ_MUL, .bot_freq = 87500, /* 87.5 MHz */ .top_freq = 108000, /* 108 MHz */ .fm_band = 0, }, /* Japan */ { .chanl_space = FM_CHANNEL_SPACING_200KHZ * FM_FREQ_MUL, .bot_freq = 76000, /* 76 MHz */ .top_freq = 90000, /* 90 MHz */ .fm_band = 1, }, }; static inline bool s610_radio_freq_is_inside_of_the_band(u32 freq, int band) { return freq >= region_configs[radio_region].bot_freq && freq <= region_configs[radio_region].top_freq; } static inline struct s610_radio * v4l2_dev_to_radio(struct v4l2_device *d) { return container_of(d, struct s610_radio, v4l2dev); } static inline struct s610_radio * v4l2_ctrl_handler_to_radio(struct v4l2_ctrl_handler *d) { return container_of(d, struct s610_radio, ctrl_handler); } /* * s610_vidioc_querycap - query device capabilities */ static int s610_radio_querycap(struct file *file, void *priv, struct v4l2_capability *capability) { struct s610_radio *radio = video_drvdata(file); FUNC_ENTRY(radio); s610_core_lock(radio->core); strlcpy(capability->driver, radio->v4l2dev.name, sizeof(capability->driver)); strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card)); snprintf(capability->bus_info, sizeof(capability->bus_info), "platform:%s", radio->v4l2dev.name); capability->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE | V4L2_CAP_READWRITE | V4L2_CAP_HW_FREQ_SEEK; capability->capabilities = capability->device_caps | V4L2_CAP_DEVICE_CAPS; s610_core_unlock(radio->core); FUNC_EXIT(radio); return 0; } static int s610_radio_enum_freq_bands(struct file *file, void *priv, struct v4l2_frequency_band *band) { int ret; struct s610_radio *radio = video_drvdata(file); FUNC_ENTRY(radio); if (band->tuner != 0) return -EINVAL; s610_core_lock(radio->core); if (band->index == S610_BAND_FM) { *band = s610_bands[radio_region]; ret = 0; } else { ret = -EINVAL; } s610_core_unlock(radio->core); FUNC_EXIT(radio); return ret; } static int s610_radio_g_tuner(struct file *file, void *priv, struct v4l2_tuner *tuner) { int ret; struct s610_radio *radio = video_drvdata(file); u16 payload; FUNC_ENTRY(radio); if (tuner->index != 0) return -EINVAL; s610_core_lock(radio->core); tuner->type = V4L2_TUNER_RADIO; tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_HWSEEK_BOUNDED | V4L2_TUNER_CAP_HWSEEK_WRAP | V4L2_TUNER_CAP_HWSEEK_PROG_LIM; strlcpy(tuner->name, "FM", sizeof(tuner->name)); tuner->rxsubchans = V4L2_TUNER_SUB_RDS; tuner->capability |= V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | V4L2_TUNER_CAP_FREQ_BANDS; /* Read register : MONO, Stereo mode */ /*ret = low_get_most_mode(radio, &payload);*/ payload = radio->low->fm_state.force_mono ? 0 : MODE_MASK_MONO_STEREO; radio->audmode = payload; tuner->audmode = radio->audmode; tuner->afc = 1; tuner->rangelow = s610_bands[radio_region].rangelow; tuner->rangehigh = s610_bands[radio_region].rangehigh; ret = low_get_search_lvl(radio, &payload); if (ret != 0) { dev_err(radio->v4l2dev.dev, "Failed to read reg for SEARCH_LVL\n"); ret = -EIO; tuner->signal = 0; } else { tuner->signal = 0; if (payload & 0x80) tuner->signal = 0xFF00; else tuner->signal = 0; tuner->signal |= (payload & 0xFF); } s610_core_unlock(radio->core); FUNC_EXIT(radio); return ret; } static int s610_radio_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *tuner) { int ret; struct s610_radio *radio = video_drvdata(file); u16 payload; FUNC_ENTRY(radio); if (tuner->index != 0) return -EINVAL; s610_core_lock(radio->core); if (tuner->audmode == V4L2_TUNER_MODE_MONO || tuner->audmode == V4L2_TUNER_MODE_STEREO) radio->audmode = tuner->audmode; else radio->audmode = V4L2_TUNER_MODE_STEREO; payload = radio->audmode; ret = low_set_most_mode(radio, payload); if (ret != 0) { dev_err(radio->v4l2dev.dev, "Failed to write reg for MOST MODE clear\n"); ret = -EIO; } s610_core_unlock(radio->core); FUNC_EXIT(radio); return ret; } static int s610_radio_pretune(struct s610_radio *radio) { int ret = 0; u16 payload; FUNC_ENTRY(radio); /*ret = low_get_flag(radio, &payload);*/ payload = fm_get_flags(radio); payload = 0; ret = low_set_mute_state(radio, payload); if (ret != 0) { dev_err(radio->v4l2dev.dev, "Failed to write reg for MUTE state clean\n"); return -EIO; } payload = radio->low->fm_state.force_mono ? 0 : MODE_MASK_MONO_STEREO; payload = 0; ret = low_set_most_blend(radio, payload); if (ret != 0) { dev_err(radio->v4l2dev.dev, "Failed to write reg for MOST blend clean\n"); return -EIO; } payload = 0; fm_set_band(radio, payload); payload = 0; ret = low_set_demph_mode(radio, payload); if (ret != 0) { dev_err(radio->v4l2dev.dev, "Failed to write reg for FM_SUBADDR_DEMPH_MODE clean\n"); return -EIO; } payload = 0; radio->low->fm_state.use_rbds = ((payload & RDS_SYSTEM_MASK_RDS) != 0); radio->low->fm_state.save_eblks = ((payload & RDS_SYSTEM_MASK_EBLK) == 0); radio->low->fm_state.rds_mem_thresh = RDS_MEM_MAX_THRESH; FUNC_EXIT(radio); return ret; } static int s610_radio_g_frequency(struct file *file, void *priv, struct v4l2_frequency *f) { struct s610_radio *radio = video_drvdata(file); u32 payload; FUNC_ENTRY(radio); if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) return -EINVAL; s610_core_lock(radio->core); payload = radio->low->fm_state.freq; f->frequency = payload; f->frequency *= FAC_VALUE; s610_core_unlock(radio->core); FUNC_EXIT(radio); FDEBUG(radio, "%s():freq.tuner:%d, type:%d, freq:%d, real-freq:%d\n", __func__, f->tuner, f->type, f->frequency, f->frequency/FAC_VALUE); return 0; } int fm_set_frequency(struct s610_radio *radio, u32 freq) { unsigned long timeleft; u32 curr_frq, intr_flag; u32 curr_frq_in_khz; int ret; u32 payload; u16 payload16; FUNC_ENTRY(radio); /* Calculate frequency with offset and set*/ payload = real_to_api(freq); low_set_freq(radio, payload); /* Read flags - just to clear any pending interrupts if we had */ payload = fm_get_flags(radio); /* Enable FR, BL interrupts */ intr_flag = radio->irq_mask; radio->irq_mask = (FM_EVENT_TUNED | FM_EVENT_BD_LMT); low_get_search_lvl(radio, (u16 *) &payload16); if (!payload16) { payload16 = FM_DEFAULT_RSSI_THRESHOLD; low_set_search_lvl(radio, (u16) payload16); } if (radio->low->fm_config.search_conf.normal_ifca_m == 0) radio->low->fm_config.search_conf.normal_ifca_m = low_fm_conf_init.search_conf.normal_ifca_m; if (radio->low->fm_config.search_conf.weak_ifca_m == 0) radio->low->fm_config.search_conf.weak_ifca_m = low_fm_conf_init.search_conf.weak_ifca_m; FDEBUG(radio, "%s(): ifcount:W-%d N-%d\n", __func__, radio->low->fm_config.search_conf.weak_ifca_m, radio->low->fm_config.search_conf.normal_ifca_m); if (!radio->low->fm_config.mute_coeffs_soft) { radio->low->fm_config.mute_coeffs_soft = low_fm_conf_init.mute_coeffs_soft; } if (!radio->low->fm_config.blend_coeffs_soft) { radio->low->fm_config.blend_coeffs_soft = low_fm_conf_init.blend_coeffs_soft; } if (!radio->low->fm_config.blend_coeffs_switch) { radio->low->fm_config.blend_coeffs_switch = low_fm_conf_init.blend_coeffs_switch; } if (!radio->low->fm_config.blend_coeffs_dis) radio->low->fm_config.blend_coeffs_dis = low_fm_conf_init.blend_coeffs_dis; fm_set_blend_mute(radio); init_completion(&radio->flags_set_fr_comp); /* Start tune */ payload = FM_TUNER_PRESET_MODE; ret = low_set_tuner_mode(radio, payload); if (ret != 0) { ret = -EIO; goto exit; } timeleft = jiffies_to_msecs(FM_DRV_TURN_TIMEOUT); /* Wait for tune ended interrupt */ timeleft = wait_for_completion_timeout(&radio->flags_set_fr_comp, FM_DRV_TURN_TIMEOUT); if (!timeleft) { dev_err(radio->v4l2dev.dev, "Timeout(%d sec),didn't get tune ended int\n", jiffies_to_msecs(FM_DRV_TURN_TIMEOUT) / 1000); ret = -ETIMEDOUT; goto exit; } /* Read freq back to confirm */ curr_frq = radio->low->fm_state.freq; curr_frq_in_khz = curr_frq; if (curr_frq_in_khz != freq) { dev_err(radio->v4l2dev.dev, "Set Freq (%d) but requested freq (%d)\n", curr_frq_in_khz, freq); ret = -ENODATA; goto exit; } FDEBUG(radio, "%s():--> Set frequency: %d Read frequency:%d\n", __func__, freq, curr_frq_in_khz); /* Update local cache */ radio->freq = curr_frq_in_khz; exit: /* Re-enable default FM interrupts */ radio->irq_mask = intr_flag; FDEBUG(radio, "wait_atomic: %d\n", radio->wait_atomic); FUNC_EXIT(radio); return ret; } static int s610_radio_s_frequency(struct file *file, void *priv, const struct v4l2_frequency *f) { int ret; u32 freq = f->frequency; struct s610_radio *radio = video_drvdata(file); FUNC_ENTRY(radio); if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) return -EINVAL; if (!wake_lock_active(&radio->wakelock)) wake_lock(&radio->wakelock); s610_core_lock(radio->core); FDEBUG(radio, "%s():freq:%d, real-freq:%d\n", __func__, f->frequency, f->frequency/FAC_VALUE); freq /= FAC_VALUE; freq = clamp(freq, region_configs[radio_region].bot_freq, region_configs[radio_region].top_freq); if (!s610_radio_freq_is_inside_of_the_band(freq, radio_region)) { ret = -EINVAL; goto unlock; } ret = fm_set_frequency(radio, freq); unlock: s610_core_unlock(radio->core); if (wake_lock_active(&radio->wakelock)) wake_unlock(&radio->wakelock); FUNC_EXIT(radio); FDEBUG(radio, "%s():v4l2_frequency.tuner:%d,type:%d,frequency:%d\n", __func__, f->tuner, f->type, freq); return ret; } int fm_rx_seek(struct s610_radio *radio, u32 seek_upward, u32 wrap_around, u32 spacing, u32 freq_low, u32 freq_hi) { u32 curr_frq, save_freq; u32 payload, int_reason, intr_flag, tune_mode; u32 space, upward; u16 payload16; unsigned long timeleft; int ret; int bl_1st, bl_2nd, bl_3nd; FUNC_ENTRY(radio); radio->seek_freq = 0; radio->wrap_around = 0; bl_1st = 0; bl_2nd = 0; bl_3nd = 0; payload = fm_get_flags(radio); /* Set channel spacing */ if (spacing > 0 && spacing <= 50) payload = 0; /*CHANNEL_SPACING_50KHZ*/ else if (spacing > 50 && spacing <= 100) payload = 1; /*CHANNEL_SPACING_100KHZ*/ else payload = 2; /*CHANNEL_SPACING_200KHZ;*/ FDEBUG(radio, "%s(): init: spacing: %d\n", __func__, payload); radio->low->fm_tuner_state.freq_step = radio->low->fm_freq_steps[payload]; radio->region.chanl_space = 50 * (1 << payload); /* Check the offset in order to be aligned to the channel spacing*/ space = radio->region.chanl_space; /* Set search direction (0:Seek Up, 1:Seek Down) */ payload = (seek_upward ? FM_SEARCH_DIRECTION_UP : FM_SEARCH_DIRECTION_DOWN); upward = payload; radio->low->fm_state.search_down = !!(payload & SEARCH_DIR_MASK); FDEBUG(radio, "%s(): direction: %d\n", __func__, payload); save_freq = freq_low; radio->seek_freq = save_freq; tune_mode = FM_TUNER_AUTONOMOUS_SEARCH_MODE_NEXT; if (radio->low->fm_state.rssi_limit_search == 0) low_set_search_lvl(radio, (u16)FM_DEFAULT_RSSI_THRESHOLD); if (radio->low->fm_config.search_conf.normal_ifca_m == 0) radio->low->fm_config.search_conf.normal_ifca_m = low_fm_conf_init.search_conf.normal_ifca_m; if (radio->low->fm_config.search_conf.weak_ifca_m == 0) radio->low->fm_config.search_conf.weak_ifca_m = low_fm_conf_init.search_conf.weak_ifca_m; FDEBUG(radio, "%s(): ifcount:W-%d N-%d\n", __func__, radio->low->fm_config.search_conf.weak_ifca_m, radio->low->fm_config.search_conf.normal_ifca_m); again: curr_frq = freq_low; if ((freq_low == region_configs[radio_region].bot_freq) && (upward == FM_SEARCH_DIRECTION_DOWN)) curr_frq = region_configs[radio_region].top_freq; if ((freq_low == region_configs[radio_region].top_freq) && (upward == FM_SEARCH_DIRECTION_UP)) curr_frq = region_configs[radio_region].bot_freq; payload = curr_frq; low_set_freq(radio, payload); FDEBUG(radio, "%s(): curr_freq: %d, freq hi: %d\n", __func__, curr_frq, freq_hi); /* Enable FR, BL interrupts */ intr_flag = radio->irq_mask; radio->irq_mask = (FM_EVENT_TUNED | FM_EVENT_BD_LMT); low_get_search_lvl(radio, (u16 *) &payload16); if (!payload16) { payload16 = FM_DEFAULT_RSSI_THRESHOLD; low_set_search_lvl(radio, (u16) payload16); } FDEBUG(radio, "%s(): SEARCH_LVL1: 0x%x\n", __func__, payload16); if (!radio->low->fm_config.mute_coeffs_soft) { radio->low->fm_config.mute_coeffs_soft = low_fm_conf_init.mute_coeffs_soft; } if (!radio->low->fm_config.blend_coeffs_soft) { radio->low->fm_config.blend_coeffs_soft = low_fm_conf_init.blend_coeffs_soft; } if (!radio->low->fm_config.blend_coeffs_switch) { radio->low->fm_config.blend_coeffs_switch = low_fm_conf_init.blend_coeffs_switch; } if (!radio->low->fm_config.blend_coeffs_dis) radio->low->fm_config.blend_coeffs_dis = low_fm_conf_init.blend_coeffs_dis; fm_set_blend_mute(radio); reinit_completion(&radio->flags_seek_fr_comp); payload = tune_mode; FDEBUG(radio, "%s(): turn start mode: 0x%x\n", __func__, payload); ret = low_set_tuner_mode(radio, (u16) payload); if (ret != 0) return -EIO; /* Wait for tune ended interrupt */ timeleft = wait_for_completion_timeout(&radio->flags_seek_fr_comp, FM_DRV_SEEK_TIMEOUT); /* seek cancel status */ if (radio->seek_status == FM_TUNER_STOP_SEARCH_MODE) { dev_info(radio->dev, ">>> rev seek cancel"); return -ENODATA; } FDEBUG(radio, "FDEBUG > Seek done rev complete!! freq %d, irq_flag: 0x%x, bl:%d\n", radio->low->fm_state.freq, radio->irq_flag, bl_1st); int_reason = radio->low->fm_state.flags & (FM_EVENT_TUNED | FM_EVENT_BD_LMT); if ((save_freq == region_configs[radio_region].bot_freq) || (save_freq == region_configs[radio_region].top_freq)) bl_1st = 1; if ((save_freq == region_configs[radio_region].bot_freq) && (upward == FM_SEARCH_DIRECTION_UP)) { bl_1st = 0; bl_2nd = 1; tune_mode = FM_TUNER_AUTONOMOUS_SEARCH_MODE; } if ((save_freq == region_configs[radio_region].top_freq) && (upward == FM_SEARCH_DIRECTION_DOWN)) { bl_1st = 0; bl_2nd = 1; tune_mode = FM_TUNER_AUTONOMOUS_SEARCH_MODE; } if ((int_reason & FM_EVENT_BD_LMT) && (bl_1st == 0) && (bl_3nd == 0)) { if (wrap_around) { freq_low = radio->low->fm_state.freq; bl_1st = 1; if (bl_2nd) bl_3nd = 1; /* Re-enable default FM interrupts */ radio->irq_mask = intr_flag; radio->wrap_around = 1; FDEBUG(radio, "> bl set %d, %d, save %d\n", radio->seek_freq, radio->wrap_around, save_freq); goto again; } else ret = -EINVAL; } /* Read freq to know where operation tune operation stopped */ payload = radio->low->fm_state.freq; radio->freq = payload; dev_info(radio->v4l2dev.dev, "Seek freq %d\n", radio->freq); radio->seek_freq = 0; radio->wrap_around = 0; FUNC_EXIT(radio); return ret; } static int s610_radio_s_hw_freq_seek(struct file *file, void *priv, const struct v4l2_hw_freq_seek *seek) { int ret = 0; struct s610_radio *radio = video_drvdata(file); u32 seek_low, seek_hi, seek_spacing; FUNC_ENTRY(radio); if (file->f_flags & O_NONBLOCK) return -EAGAIN; if (seek->tuner != 0 || seek->type != V4L2_TUNER_RADIO) return -EINVAL; if (seek->rangelow >= seek->rangehigh) ret = -EINVAL; seek_low = radio->low->fm_state.freq; if (seek->seek_upward) seek_hi = region_configs[radio_region].top_freq; else seek_hi = region_configs[radio_region].bot_freq; seek_spacing = seek->spacing / 1000; FDEBUG(radio, "%s(): get freq low: %d, freq hi: %d\n", __func__, seek_low, seek_hi); FDEBUG(radio, "%s(): upward:%d, warp: %d, spacing: %d\n", __func__, seek->seek_upward, seek->wrap_around, seek->spacing); if (!wake_lock_active(&radio->wakelock)) wake_lock(&radio->wakelock); ret = fm_rx_seek(radio, seek->seek_upward, seek->wrap_around, seek_spacing, seek_low, seek_hi); if (ret < 0) dev_err(radio->v4l2dev.dev, "RX seek failed - %d\n", ret); if (wake_lock_active(&radio->wakelock)) wake_unlock(&radio->wakelock); FUNC_EXIT(radio); return ret; } /* Configures mute mode (Mute Off/On/Attenuate) */ int fm_set_mute_mode(struct s610_radio *radio, u8 mute_mode_toset) { u16 payload = radio->mute_mode; int ret = 0; FUNC_ENTRY(radio); radio->mute_mode = mute_mode_toset; switch (radio->mute_mode) { case FM_MUTE_ON: payload = FM_RX_AC_MUTE_MODE; break; case FM_MUTE_OFF: payload = FM_RX_UNMUTE_MODE; break; } /* Write register : mute */ ret = low_set_mute_state(radio, payload); if (ret != 0) ret = -EIO; FUNC_EXIT(radio); return ret; } /* Enable/Disable RDS */ int fm_set_rds_mode(struct s610_radio *radio, u8 rds_en_dis) { int ret; u16 payload; int ii; u32 fifo_tmp; FUNC_ENTRY(radio); if (rds_en_dis != FM_RDS_ENABLE && rds_en_dis != FM_RDS_DISABLE) { dev_err(radio->v4l2dev.dev, "Invalid rds option\n"); return -EINVAL; } if (rds_en_dis == FM_RDS_ENABLE) { if (!wake_lock_active(&radio->rdswakelock)) wake_lock(&radio->rdswakelock); mdelay(100); atomic_set(&radio->is_rds_new, 0); radio->rds_cnt_mod = 0; radio->rds_n_count = 0; radio->rds_r_count = 0; radio->rds_read_cnt = 0; radio->rds_sync_loss_cnt = 0; init_waitqueue_head(&radio->core->rds_read_queue); payload = radio->core->power_mode; payload |= S610_POWER_ON_RDS; ret = s610_core_set_power_state(radio, payload); if (ret != 0) { dev_err(radio->v4l2dev.dev, "Failed to set for RDS power state\n"); return -EIO; } /* Write register : RDS on */ /* Turn on RDS and RDS circuit */ fm_rds_on(radio); /* Write register : clear RDS cache */ /* flush RDS buffer */ payload = FM_RX_RDS_FLUSH_FIFO; ret = low_set_rds_cntr(radio, payload); if (ret != 0) { dev_err(radio->v4l2dev.dev, "Failed to write reg for default RDS_CNTR\n"); return -EIO; } /* Write register : clear panding interrupt flags */ /* Read flags - just to clear any pending interrupts. */ payload = fm_get_flags(radio); /* Write register : set RDS memory depth */ /* Set RDS FIFO threshold value */ if (radio->rds_parser_enable) radio->low->fm_state.rds_mem_thresh = RDS_MEM_MAX_THRESH_PARSER; else radio->low->fm_state.rds_mem_thresh = RDS_MEM_MAX_THRESH; /* Write register : set RDS interrupt enable */ /* Enable RDS interrupt */ radio->irq_mask |= FM_EVENT_BUF_FUL; /* Update our local flag */ radio->rds_flag = FM_RDS_ENABLE; /* RDS parser reset */ if (radio->rds_parser_enable) fm_rds_parser_reset(&(radio->pi)); /* FIFO clear */ for (ii = 0; ii < 32; ii++) fifo_tmp = fmspeedy_get_reg_work(0xFFF3C0); #ifdef RDS_POLLING_ENABLE fm_rds_periodic_update((unsigned long) radio); #endif /*RDS_POLLING_ENABLE*/ } else if ( rds_en_dis == FM_RDS_DISABLE) { payload = radio->core->power_mode; payload &= ~S610_POWER_ON_RDS; ret = s610_core_set_power_state(radio, payload); if (ret != 0) { dev_err(radio->v4l2dev.dev, "Failed to set for RDS power state\n"); return -EIO; } /* Write register : RDS off */ /* Turn off RX RDS */ fm_rds_off(radio); /* Update RDS local cache */ radio->irq_mask &= ~(FM_EVENT_BUF_FUL); radio->rds_flag = FM_RDS_DISABLE; #ifdef RDS_POLLING_ENABLE fm_rds_periodic_cancel((unsigned long) radio); #endif /*RDS_POLLING_ENABLE*/ /* Service pending read */ wake_up_interruptible(&radio->core->rds_read_queue); if (wake_lock_active(&radio->rdswakelock)) wake_unlock(&radio->rdswakelock); } FUNC_EXIT(radio); return ret; } int fm_set_deemphasis(struct s610_radio *radio, u16 vol_to_set) { int ret = 0; u16 payload; payload = vol_to_set; /* Write register : deemphasis */ ret = low_set_demph_mode(radio, payload); if (ret != 0) return -EIO; radio->deemphasis_mode = vol_to_set; return ret; } static int s610_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl) { struct s610_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); u16 payload; int ret = 0; FUNC_ENTRY(radio); if (!radio) return -ENODEV; s610_core_lock(radio->core); switch (ctrl->id) { case V4L2_CID_S610_CH_SPACING: payload = radio->low->fm_tuner_state.freq_step / 100; FDEBUG(radio, "%s(), FREQ_STEP val:%d, ret : %d\n", __func__, payload, ret); ctrl->val = payload; break; case V4L2_CID_S610_CH_BAND: payload = radio->low->fm_state.band; FDEBUG(radio, "%s(), BAND val:%d, ret : %d\n", __func__, payload, ret); ctrl->val = payload; break; case V4L2_CID_S610_SOFT_STEREO_BLEND: payload = radio->low->fm_state.use_switched_blend ? MODE_MASK_BLEND : 0; FDEBUG(radio, "%s(), MOST_BLEND val:%d, ret : %d\n", __func__, ctrl->val, ret); ctrl->val = payload; break; case V4L2_CID_S610_SOFT_STEREO_BLEND_COEFF: payload = radio->low->fm_config.blend_coeffs_soft; FDEBUG(radio, "%s(),BLEND_COEFF_SOFT val:%d, ret: %d\n", __func__, ctrl->val, ret); ctrl->val = payload; break; case V4L2_CID_S610_SOFT_MUTE_COEFF: payload = radio->low->fm_config.mute_coeffs_soft; FDEBUG(radio, "%s(), MUTE_COEFF_SOFT val:%d, ret: %d\n", __func__, ctrl->val, ret); ctrl->val = payload; break; case V4L2_CID_S610_RSSI_CURR: fm_update_rssi(radio); ctrl->val = radio->low->fm_state.rssi; FDEBUG(radio, "%s(), RSSI_CURR val:%d, ret : %d\n", __func__, ctrl->val, ret); break; case V4L2_CID_S610_SNR_CURR: radio->low->fm_state.snr = fmspeedy_get_reg(0xFFF2C5); payload = radio->low->fm_state.snr; FDEBUG(radio, "%s(), SNR_CURR val:%d, ret : %d\n", __func__, payload, ret); ctrl->val = payload; break; case V4L2_CID_S610_SEEK_CANCEL: ctrl->val = 0; break; case V4L2_CID_S610_SEEK_MODE: if (radio->seek_mode == FM_TUNER_AUTONOMOUS_SEARCH_MODE_NEXT) ctrl->val = 4; else ctrl->val = radio->seek_mode; FDEBUG(radio, "%s(), SEEK_MODE val:%d, ret : %d\n", __func__, ctrl->val, ret); break; case V4L2_CID_S610_RDS_ON: if (radio->low->fm_state.fm_pwr_on & S610_POWER_ON_RDS) ctrl->val = FM_RDS_ENABLE; else ctrl->val = FM_RDS_DISABLE; FDEBUG(radio, "%s(), RDS_ON:%d, ret: %d\n", __func__, ctrl->val, ret); break; case V4L2_CID_S610_IF_COUNT1: payload = radio->low->fm_config.search_conf.weak_ifca_m; FDEBUG(radio, "%s(), IF_CNT1 val:%d, ret : %d\n", __func__, ctrl->val, ret); ctrl->val = payload; break; case V4L2_CID_S610_IF_COUNT2: payload = radio->low->fm_config.search_conf.normal_ifca_m; FDEBUG(radio, "%s(), IF_CNT2 val:%d, ret : %d\n", __func__, ctrl->val, ret); ctrl->val = payload; break; case V4L2_CID_S610_RSSI_TH: ctrl->val = radio->low->fm_state.rssi; FDEBUG(radio, "%s(), RSSI_CURR val:%d, ret : %d\n", __func__, ctrl->val, ret); break; case V4L2_CID_S610_KERNEL_VER: ctrl->val |= FM_RADIO_RDS_PARSER_VER_CHECK; FDEBUG(radio, "%s(), KERNEL_VER val:%d, ret : %d\n", __func__, ctrl->val, ret); break; case V4L2_CID_S610_SOFT_STEREO_BLEND_REF: ctrl->val = radio->rssi_ref_enable; FDEBUG(radio, "%s(), SOFT_STEREO_BLEND_REF val:%d, ret : %d\n", __func__, ctrl->val, ret); break; default: ret = -EINVAL; dev_err(radio->v4l2dev.dev, "g_volatile_ctrl Unknown IOCTL: %d\n", (int) ctrl->id); break; } s610_core_unlock(radio->core); FUNC_EXIT(radio); return ret; } static int s610_radio_s_ctrl(struct v4l2_ctrl *ctrl) { int ret = 0; u16 payload = 0; struct s610_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); FUNC_ENTRY(radio); s610_core_lock(radio->core); switch (ctrl->id) { case V4L2_CID_AUDIO_MUTE: /* set mute */ ret = fm_set_mute_mode(radio, (u8)ctrl->val); FDEBUG(radio, "%s(), mute val:%d, ret : %d\n", __func__, ctrl->val, ret); if (ret != 0) ret = -EIO; break; case V4L2_CID_AUDIO_VOLUME: /* set volume gain */ fm_set_audio_gain(radio, (u8)ctrl->val); FDEBUG(radio, "%s(), volume val:%d, ret : %d\n", __func__, ctrl->val, ret); break; case V4L2_CID_TUNE_DEEMPHASIS: if (ctrl->val == 0) break; payload = (u16)ctrl->val; if (ctrl->val > 0) payload -= 1; ret = fm_set_deemphasis(radio, payload); FDEBUG(radio, "%s(), deemphasis val:%d, ret : %d, payload:%d\n", __func__, ctrl->val, ret, payload); if (ret != 0) ret = -EINVAL; break; case V4L2_CID_S610_CH_SPACING: radio->freq_step = ctrl->val; radio->low->fm_tuner_state.freq_step = radio->low->fm_freq_steps[ctrl->val]; FDEBUG(radio, "%s(), FREQ_STEP val:%d, ret : %d\n", __func__, ctrl->val, ret); break; case V4L2_CID_S610_CH_BAND: radio_region = ctrl->val; radio->radio_region = ctrl->val; payload = ctrl->val; fm_set_band(radio, payload); FDEBUG(radio, "%s(), BAND val:%d, ret : %d\n", __func__, ctrl->val, ret); break; case V4L2_CID_S610_SOFT_STEREO_BLEND: ret = low_set_most_blend(radio, ctrl->val); FDEBUG(radio, "%s(), MOST_BLEND val:%d, ret : %d\n", __func__, ctrl->val, ret); if (ret != 0) ret = -EIO; break; case V4L2_CID_S610_SOFT_STEREO_BLEND_COEFF: radio->low->fm_config.blend_coeffs_soft = (u16)ctrl->val; fm_set_blend_mute(radio); FDEBUG(radio, "%s(), BLEND_COEFF_SOFT val:%d,ret: %d\n", __func__, ctrl->val, ret); break; case V4L2_CID_S610_SOFT_MUTE_COEFF: radio->low->fm_config.mute_coeffs_soft = (u16)ctrl->val; fm_set_blend_mute(radio); FDEBUG(radio, "%s(), SOFT_MUTE_COEFF val:%d, ret: %d\n", __func__, ctrl->val, ret); break; case V4L2_CID_S610_RSSI_CURR: ctrl->val = 0; break; case V4L2_CID_S610_SEEK_CANCEL: if (ctrl->val) { payload = FM_TUNER_STOP_SEARCH_MODE; low_set_tuner_mode(radio, payload); FDEBUG(radio, "%s(), SEEK_CANCEL val:%d, ret: %d\n", __func__, ctrl->val, ret); radio->seek_mode = FM_TUNER_STOP_SEARCH_MODE; } break; case V4L2_CID_S610_SEEK_MODE: if (ctrl->val == 4) radio->seek_mode = FM_TUNER_AUTONOMOUS_SEARCH_MODE_NEXT; else radio->seek_mode = ctrl->val; FDEBUG(radio, "%s(), SEEK_MODE val:%d, ret : %d\n", __func__, ctrl->val, ret); break; case V4L2_CID_S610_RDS_ON: payload = (u16)ctrl->val; if (payload & RDS_PARSER_ENABLE) { radio->rds_parser_enable = TRUE; } else { radio->rds_parser_enable = FALSE; } payload &= FM_RDS_ENABLE; ret = fm_set_rds_mode(radio, payload); FDEBUG(radio, "%s(), RDS_RECEPTION:%d, ret:%d parser:%d\n", __func__, payload, ret, radio->rds_parser_enable); if (ret != 0) ret = -EINVAL; break; case V4L2_CID_S610_SNR_CURR: ctrl->val = 0; break; case V4L2_CID_S610_IF_COUNT1: radio->low->fm_config.search_conf.weak_ifca_m = (u16)ctrl->val; FDEBUG(radio, "%s(), IF_CNT1 val:%d, ret : %d\n", __func__, ctrl->val, ret); break; case V4L2_CID_S610_IF_COUNT2: radio->low->fm_config.search_conf.normal_ifca_m = (u16)ctrl->val; FDEBUG(radio, "%s(), IF_CNT2 val:%d, ret : %d\n", __func__, ctrl->val, ret); break; case V4L2_CID_S610_RSSI_TH: low_set_search_lvl(radio, (u16)ctrl->val); FDEBUG(radio, "%s(), RSSI_TH val:%d, ret : %d\n", __func__, ctrl->val, ret); break; case V4L2_CID_S610_KERNEL_VER: ctrl->val = 0; break; case V4L2_CID_S610_SOFT_STEREO_BLEND_REF: payload = (u16)ctrl->val; if (payload & RSSI_REF_ENABLE) radio->rssi_ref_enable = TRUE; else radio->rssi_ref_enable = FALSE; FDEBUG(radio, "%s(), SOFT_STEREO_BLEND_REF val:%d, ret : %d\n", __func__, ctrl->val, ret); break; default: ret = -EINVAL; dev_err(radio->v4l2dev.dev, "s_ctrl Unknown IOCTL: 0x%x\n", (int)ctrl->id); break; } s610_core_unlock(radio->core); FUNC_EXIT(radio); return ret; } #ifdef CONFIG_VIDEO_ADV_DEBUG static int s610_radio_g_register(struct file *file, void *fh, struct v4l2_dbg_register *reg) { struct s610_radio *radio = video_drvdata(file); FUNC_ENTRY(radio); s610_core_lock(radio->core); reg->size = 4; reg->val = (__u64)fmspeedy_get_reg((u32)reg->reg); s610_core_unlock(radio->core); FUNC_EXIT(radio); return 0; } static int s610_radio_s_register(struct file *file, void *fh, const struct v4l2_dbg_register *reg) { struct s610_radio *radio = video_drvdata(file); FUNC_ENTRY(radio); s610_core_lock(radio->core); fmspeedy_set_reg((u32)reg->reg, (u32)reg->val); s610_core_unlock(radio->core); FUNC_EXIT(radio); return 0; } #endif int s610_core_set_power_state(struct s610_radio *radio, u8 next_state) { int ret = 0; FUNC_ENTRY(radio); ret = low_set_power(radio, next_state); if (ret != 0) { dev_err(radio->v4l2dev.dev, "Failed to write reg for power on\n"); ret = -EIO; goto ret_power; } radio->core->power_mode |= next_state; ret_power: FUNC_EXIT(radio); return ret; } void fm_prepare(struct s610_radio *radio) { FUNC_ENTRY(radio); spin_lock_init(&radio->rds_buff_lock); /* Initial FM device variables */ /* Region info */ radio->region = region_configs[radio_region]; radio->mute_mode = FM_MUTE_OFF; radio->rf_depend_mute = FM_RX_RF_DEPENDENT_MUTE_OFF; radio->rds_flag = FM_RDS_DISABLE; radio->freq = FM_UNDEFINED_FREQ; radio->rds_mode = FM_RDS_SYSTEM_RDS; radio->af_mode = FM_RX_RDS_AF_SWITCH_MODE_OFF; radio->core->power_mode = S610_POWER_DOWN; radio->seek_weak_rssi = SCAN_WEAK_SIG_THRESHOLD; /* Reset RDS buffer cache if need */ /* Initial wait queue for rds read */ init_waitqueue_head(&radio->core->rds_read_queue); radio->idle_cnt_mod = 0; radio->rds_new_stat = 0; FUNC_EXIT(radio); } #ifdef USE_FM_LNA_ENABLE static int set_eLNA_gpio(struct s610_radio *radio, int stat) { if (radio->without_elna) return -EPERM; if (!gpio_is_valid(radio->elna_gpio)) { return -EINVAL; } else { dev_info(radio->v4l2dev.dev, "%s(%d)\n", __func__, stat); gpio_set_value(radio->elna_gpio, stat); } dev_info(radio->v4l2dev.dev, "Get elna_gpio(%d)\n", gpio_get_value(radio->elna_gpio)); return 0; } #endif /* USE_FM_LNA_ENABLE */ static int s610_radio_fops_open(struct file *file) { int ret; struct s610_radio *radio = video_drvdata(file); static bool run_once = true; FUNC_ENTRY(radio); shared_fm_open_cnt++; ret = v4l2_fh_open(file); if (ret) return ret; if (v4l2_fh_is_singular_file(file)) { #ifdef CONFIG_SCSC_FM /* Start FM/WLBT LDO */ ret = mx250_fm_request(); if (ret < 0) { dev_err(radio->v4l2dev.dev, "mx250_fm_request() failed with err %d\n", ret); return ret; } #endif /* CONFIG_SCSC_FM */ #ifdef USE_FM_LNA_ENABLE if (radio->elna_gpio != -EINVAL) { dev_info(radio->v4l2dev.dev, "(%s):FM eLNA gpio: %d\n", __func__, radio->elna_gpio); if (gpio_request_one(radio->elna_gpio, GPIOF_OUT_INIT_LOW, "LNA_GPIO_EN")) { dev_info(radio->v4l2dev.dev, "Failed to request gpio for FM eLNA\n"); } if (set_eLNA_gpio(radio, GPIO_HIGH)) { dev_info(radio->v4l2dev.dev, "Failed to set gpio HIGH for FM eLNA\n"); } else dev_info(radio->v4l2dev.dev, "Set gpio HIGH for FM eLNA\n"); } #endif /* USE_FM_LNA_ENABLE */ s610_core_lock(radio->core); atomic_set(&radio->is_doing, 0); atomic_set(&radio->is_rds_doing, 0); #ifdef USE_AUDIO_PM if (radio->a_dev) { ret = pm_runtime_get_sync(radio->a_dev); if (ret < 0) { dev_err(radio->v4l2dev.dev, "audio get_sync not work: not suspend %d\n", ret); goto err_open; } abox_request_cpu_gear_sync(radio->a_dev, dev_get_drvdata(radio->a_dev), ABOX_CPU_GEAR_FM, ABOX_CPU_GEAR_MAX); } #endif /* USE_AUDIO_PM */ ret = pm_runtime_get_sync(radio->dev); if (ret < 0) { dev_err(radio->v4l2dev.dev, "get_sync failed with err %d\n", ret); goto err_open; } fmspeedy_wakeup(); if (run_once) { fm_ds_set(0); fm_aux_pll_off(); run_once = false; } fm_get_version_number(); /* Initail fm low structure */ ret = init_low_struc(radio); if (ret < 0) { dev_err(radio->v4l2dev.dev, "Failed to init reg for initial struc\n"); goto err_open; } /* Booting fm */ ret = fm_boot(radio); if (ret < 0) { dev_err(radio->v4l2dev.dev, "Failed to set reg for FM boot\n"); goto err_open; } fm_prepare(radio); ret = s610_core_set_power_state(radio, S610_POWER_ON_FM); if (ret < 0) { dev_err(radio->v4l2dev.dev, "Failed to write reg for power on\n"); goto err_open; } ret = s610_radio_pretune(radio); if (ret < 0) { dev_err(radio->v4l2dev.dev, "Failed to write reg for preturn\n"); goto power_down; } radio->core->power_mode = S610_POWER_ON_FM; mdelay(100); #ifndef RDS_POLLING_ENABLE /* Speedy master interrupt enable */ fm_speedy_m_int_enable(); #else fm_speedy_m_int_disable(); #endif /*RDS_POLLING_ENABLE*/ s610_core_unlock(radio->core); /*Must be done after s610_core_unlock to prevent a deadlock*/ v4l2_ctrl_handler_setup(&radio->ctrl_handler); } FUNC_EXIT(radio); return ret; power_down: s610_core_set_power_state(radio, S610_POWER_DOWN); err_open: s610_core_unlock(radio->core); v4l2_fh_release(file); FUNC_EXIT(radio); return ret; } static int s610_radio_fops_release(struct file *file) { int ret = 0; struct s610_radio *radio = video_drvdata(file); FUNC_ENTRY(radio); if (v4l2_fh_is_singular_file(file)) { s610_core_lock(radio->core); s610_core_set_power_state(radio, S610_POWER_DOWN); /* Speedy master interrupt disable */ fm_speedy_m_int_disable(); /* FM demod power off */ fm_power_off(); cancel_delayed_work_sync(&radio->dwork_sig2); cancel_delayed_work_sync(&radio->dwork_tune); #ifdef ENABLE_RDS_WORK_QUEUE cancel_work_sync(&radio->work); #endif /*ENABLE_RDS_WORK_QUEUE*/ #ifdef IDLE_POLLING_ENABLE fm_idle_periodic_cancel((unsigned long) radio); #endif /*IDLE_POLLING_ENABLE*/ #ifdef RDS_POLLING_ENABLE fm_rds_periodic_cancel((unsigned long) radio); #endif /*RDS_POLLING_ENABLE*/ /*#ifdef ENABLE_IF_WORK_QUEUE cancel_work_sync(&radio->if_work); #endif*/ /*ENABLE_IF_WORK_QUEUE*/ pm_runtime_put_sync(radio->dev); #ifdef USE_AUDIO_PM if (radio->a_dev) { abox_request_cpu_gear_sync(radio->a_dev, dev_get_drvdata(radio->a_dev), ABOX_CPU_GEAR_FM, ABOX_CPU_GEAR_MIN); pm_runtime_put_sync(radio->a_dev); } #endif /* USE_AUDIO_PM */ s610_core_unlock(radio->core); #ifdef USE_FM_LNA_ENABLE if (radio->elna_gpio != -EINVAL) { if (set_eLNA_gpio(radio, GPIO_LOW)) dev_info(radio->v4l2dev.dev, "Failed to set gpio LOW for FM eLNA\n"); else dev_info(radio->v4l2dev.dev, "Set gpio LOW for FM eLNA\n"); gpio_free(radio->elna_gpio); } #endif /* USE_FM_LNA_ENABLE */ #ifdef CONFIG_SCSC_FM /* Stop FM/WLBT LDO */ ret = mx250_fm_release(); if (ret < 0) dev_err(radio->v4l2dev.dev, "mx250_fm_release() failed with err %d\n", ret); #endif } if (wake_lock_active(&radio->wakelock)) wake_unlock(&radio->wakelock); if (wake_lock_active(&radio->rdswakelock)) wake_unlock(&radio->rdswakelock); ret = v4l2_fh_release(file); shared_fm_open_cnt--; return ret; } static ssize_t s610_radio_fops_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { int ret; size_t rsize, blocks; struct s610_radio *radio = video_drvdata(file); size_t rds_max_thresh; FUNC_ENTRY(radio); ret = wait_event_interruptible(radio->core->rds_read_queue, atomic_read(&radio->is_rds_new)); if (ret < 0) return -EINTR; if (!atomic_read(&radio->is_rds_new)) { radio->rds_new_stat++; return -ERESTARTSYS; } if (s610_core_lock_interruptible(radio->core)) return -ERESTARTSYS; if (radio->rds_parser_enable) #ifdef USE_RINGBUFF_API rds_max_thresh = ringbuf_bytes_used(&radio->rds_rb); #else rds_max_thresh = RDS_MEM_MAX_THRESH_PARSER; #endif /* USE_RINGBUFF_API */ else rds_max_thresh = RDS_MEM_MAX_THRESH; /* Turn on RDS mode */ memset(radio->rds_buf, 0, 480); rsize = rds_max_thresh; rsize = min(rsize, count); blocks = rsize/FM_RDS_BLK_SIZE; ret = fm_read_rds_data(radio, radio->rds_buf, (int)rsize, (u16 *)&blocks); if (ret == 0) { dev_err(radio->v4l2dev.dev, ">>> Failed to read rds mode\n"); goto read_unlock; } /* always transfer rds complete blocks */ if (copy_to_user(buf, &radio->rds_buf, rsize)) ret = -EFAULT; if (radio->rds_parser_enable) { RDSEBUG(radio, "RDS RD done:%08d:%08d fifo err:%08d type(%02X) len(%02X)", radio->rds_read_cnt, radio->rds_n_count, radio->rds_fifo_err_cnt, radio->rds_buf[0], radio->rds_buf[1]); } else { if ((radio->rds_buf[2]&0x07) != RDS_BLKTYPE_A) { radio->rds_reset_cnt++; if (radio->rds_reset_cnt > radio->low->fm_state.rds_unsync_blk_cnt) { fm_set_rds_mode(radio, FM_RDS_DISABLE); mdelay(10); fm_set_rds_mode(radio, FM_RDS_ENABLE); radio->rds_reset_cnt = 0; RDSEBUG(radio, "RDS reset! cause of block type invalid"); } RDSEBUG(radio, "RDS block type invalid! %02d, %08d", (radio->rds_buf[2]&0x07), radio->rds_reset_cnt); } RDSEBUG(radio, "RDS RD done:%08d:%08d fifo err:%08d block type0:%02X,%02X", radio->rds_read_cnt, radio->rds_n_count, radio->rds_fifo_err_cnt, (radio->rds_buf[2]&0x11)>>3, radio->rds_buf[2]&0x07); } radio->rds_read_cnt++; ret = rsize; read_unlock: atomic_set(&radio->is_rds_new, 0); atomic_set(&gradio->is_doing, 0); atomic_set(&gradio->is_rds_doing, 0); s610_core_unlock(radio->core); FUNC_EXIT(radio); return ret; } static unsigned int s610_radio_fops_poll(struct file *file, struct poll_table_struct *pts) { struct s610_radio *radio = video_drvdata(file); unsigned long req_events = poll_requested_events(pts); unsigned int ret = v4l2_ctrl_poll(file, pts); FUNC_ENTRY(radio); if (req_events & (POLLIN | POLLRDNORM)) { poll_wait(file, &radio->core->rds_read_queue, pts); if (atomic_read(&radio->is_rds_new)) ret = POLLIN | POLLRDNORM; } FDEBUG(radio, "POLL RET: 0x%x pwmode: %d freq: %d", ret, radio->core->power_mode, radio->freq); FUNC_EXIT(radio); return ret; } static const struct v4l2_file_operations s610_fops = { .owner = THIS_MODULE, .read = s610_radio_fops_read, .poll = s610_radio_fops_poll, .unlocked_ioctl = video_ioctl2, .open = s610_radio_fops_open, .release = s610_radio_fops_release, }; static const struct v4l2_ioctl_ops S610_ioctl_ops = { .vidioc_querycap = s610_radio_querycap, .vidioc_g_tuner = s610_radio_g_tuner, .vidioc_s_tuner = s610_radio_s_tuner, .vidioc_g_frequency = s610_radio_g_frequency, .vidioc_s_frequency = s610_radio_s_frequency, .vidioc_s_hw_freq_seek = s610_radio_s_hw_freq_seek, .vidioc_enum_freq_bands = s610_radio_enum_freq_bands, .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, #ifdef CONFIG_VIDEO_ADV_DEBUG .vidioc_g_register = s610_radio_g_register, .vidioc_s_register = s610_radio_s_register, #endif }; static const struct video_device s610_viddev_template = { .fops = &s610_fops, .name = DRIVER_NAME, .release = video_device_release_empty, }; static int s610_radio_add_new_custom(struct s610_radio *radio, enum s610_ctrl_idx idx, unsigned long flag) { int ret; struct v4l2_ctrl *ctrl; ctrl = v4l2_ctrl_new_custom(&radio->ctrl_handler, &s610_ctrls[idx], NULL); ret = radio->ctrl_handler.error; if (ctrl == NULL && ret) { dev_err(radio->v4l2dev.dev, "Could not initialize '%s' control %d\n", s610_ctrls[idx].name, ret); return -EINTR; } if (flag && (ctrl != NULL)) ctrl->flags |= flag; return ret; } #ifndef RDS_POLLING_ENABLE static irqreturn_t s610_hw_irq_handle(int irq, void *devid) { struct s610_radio *radio = devid; u32 int_stat; spin_lock(&radio->slock); int_stat = __raw_readl(radio->fmspeedy_base+FMSPDY_STAT); if (int_stat & FM_SLV_INT) fm_isr(radio); spin_unlock(&radio->slock); return IRQ_HANDLED; } #endif /* RDS_POLLING_ENABLE */ MODULE_ALIAS("platform:s610-radio"); static const struct of_device_id exynos_fm_of_match[] = { { .compatible = "samsung,exynos9610-fm", }, {}, }; MODULE_DEVICE_TABLE(of, exynos_fm_of_match); signed int exynos_get_fm_open_status(void) { pr_info("%s(): shared fm open count: %d", __func__, shared_fm_open_cnt); return (shared_fm_open_cnt); } EXPORT_SYMBOL(exynos_get_fm_open_status); /* bit 28: Global hardware automatic clock gating enable on CMU module : CMU */ #define ENABLE_AUTOMATIC_CLKGATING 28 void disable_clk_gating(struct s610_radio *radio) { void __iomem *cmu_disaud_800; u32 cmu_disaud_800_val; FUNC_ENTRY(radio); cmu_disaud_800 = radio->disaud_cmu_base+0x800; /* Read register */ cmu_disaud_800_val = read32(cmu_disaud_800); FDEBUG(radio, "cmu_disaud_800: 0x%08X,\n", cmu_disaud_800_val); if (GetBits(cmu_disaud_800, ENABLE_AUTOMATIC_CLKGATING, 1)) SetBits(cmu_disaud_800, ENABLE_AUTOMATIC_CLKGATING, 1, 0); /* Read register */ cmu_disaud_800_val = read32(cmu_disaud_800); FDEBUG(radio, "cmu_disaud_800: 0x%08X,\n", cmu_disaud_800_val); FUNC_EXIT(radio); } void enable_FM_mux_clk_aud(struct s610_radio *radio) { void __iomem *cmu_disaud_100c; u32 cmu_disaud_100c_val; FUNC_ENTRY(radio); cmu_disaud_100c = radio->disaud_cmu_base+0x100C; /* Read register */ cmu_disaud_100c_val = read32(cmu_disaud_100c); FDEBUG(radio, "befor: cmu_disaud_100c: 0x%08X,\n", cmu_disaud_100c_val); if (GetBits(cmu_disaud_100c, ENABLE_AUTOMATIC_CLKGATING, 1)) SetBits(cmu_disaud_100c, ENABLE_AUTOMATIC_CLKGATING, 1, 0); /* Read register */ cmu_disaud_100c_val = read32(cmu_disaud_100c); FDEBUG(radio, "after: cmu_disaud_100c: 0x%08X,\n", cmu_disaud_100c_val); FUNC_EXIT(radio); } #ifdef USE_AUDIO_PM static struct device_node *exynos_audio_parse_dt(struct s610_radio *radio) { struct platform_device *pdev = NULL; struct device_node *np = NULL; np = of_find_compatible_node(NULL, NULL, "samsung,abox"); if (!np) { dev_err(radio->dev, "abox device is not available\n"); return NULL; } pdev = of_find_device_by_node(np); if (!pdev) { dev_err(radio->dev, "%s: failed to get audio platform_device\n", __func__); return NULL; } radio->a_dev = &pdev->dev; return np; } #endif /* USE_AUDIO_PM */ static int s610_radio_probe(struct platform_device *pdev) { unsigned long ret; struct s610_radio *radio; struct v4l2_ctrl *ctrl; const struct of_device_id *match; struct resource *resource; struct device *dev = &pdev->dev; static atomic_t instance = ATOMIC_INIT(0); struct device_node *dnode; dnode = dev->of_node; dev_info(&pdev->dev, ">>> start FM Radio probe\n"); radio = devm_kzalloc(&pdev->dev, sizeof(*radio), GFP_KERNEL); if (!radio) return -ENOMEM; radio->core = devm_kzalloc(&pdev->dev, sizeof(struct s610_core), GFP_KERNEL); if (!radio->core) { ret = -ENOMEM; goto alloc_err0; } radio->low = devm_kzalloc(&pdev->dev, sizeof(struct s610_low), GFP_KERNEL); if (!radio->low) { ret = -ENOMEM; goto alloc_err1; } radio->dev = dev; radio->pdev = pdev; gradio = radio; ret = fm_clk_get(radio); if (ret) goto alloc_err2; fm_clk_prepare(radio); if (ret) goto alloc_err2; fm_clk_enable(radio); if (ret) { fm_clk_unprepare(radio); goto alloc_err2; } pm_runtime_set_active(dev); pm_runtime_enable(dev); spin_lock_init(&radio->slock); spin_lock_init(&radio->rds_lock); /* Init flags FR BL init_completion */ init_completion(&radio->flags_set_fr_comp); init_completion(&radio->flags_seek_fr_comp); v4l2_device_set_name(&radio->v4l2dev, DRIVER_NAME, &instance); ret = v4l2_device_register(&pdev->dev, &radio->v4l2dev); if (ret) { dev_err(&pdev->dev, "Cannot register v4l2_device.\n"); goto alloc_err3; } match = of_match_node(exynos_fm_of_match, dev->of_node); if (!match) { dev_err(&pdev->dev, "failed to match node\n"); ret = -EINVAL; goto alloc_err4; } resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); radio->fmspeedy_base = devm_ioremap_resource(&pdev->dev, resource); if (IS_ERR(radio->fmspeedy_base)) { dev_err(&pdev->dev, "Failed to request memory region\n"); ret = -EBUSY; goto alloc_err4; } resource = platform_get_resource(pdev, IORESOURCE_MEM, 1); radio->disaud_cmu_base = devm_ioremap_resource(&pdev->dev, resource); if (IS_ERR(radio->disaud_cmu_base)) { dev_err(&pdev->dev, "Failed to request memory region\n"); ret = -EBUSY; goto alloc_err4; } #ifndef RDS_POLLING_ENABLE /*save to global variavle fm speedy physical address */ resource = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!resource) { dev_err(&pdev->dev, "failed to get IRQ resource\n"); ret = -ENOENT; goto alloc_err4; } ret = devm_request_irq(&pdev->dev, resource->start, s610_hw_irq_handle, 0, pdev->name, radio); if (ret) { dev_err(&pdev->dev, "failed to install irq\n"); goto alloc_err4; } radio->irq = resource->start; #endif /* RDS_POLLING_ENABLE */ #ifdef USE_FM_LNA_ENABLE radio->elna_gpio = of_get_named_gpio(dnode, "elna_gpio", 0); if (!gpio_is_valid(radio->elna_gpio)) { dev_err(dev, "(%s) elna_gpio invalid. Disable elna_gpio control\n", __func__); radio->elna_gpio = -EINVAL; } else { ret = gpio_request_one(radio->elna_gpio, GPIOF_OUT_INIT_LOW, "LNA_GPIO_EN"); if (ret) gpio_set_value(radio->elna_gpio, GPIO_LOW); dev_info(dev, "(%s) Enable elna_gpio control :%d\n", __func__, gpio_get_value(radio->elna_gpio)); } #endif /* USE_FM_LNA_ENABLE */ #ifdef USE_AUDIO_PM if (!exynos_audio_parse_dt(radio)) { ret = -EINVAL; goto alloc_err4; } #endif /* USE_AUDIO_PM */ memcpy(&radio->videodev, &s610_viddev_template, sizeof(struct video_device)); radio->videodev.v4l2_dev = &radio->v4l2dev; radio->videodev.ioctl_ops = &S610_ioctl_ops; video_set_drvdata(&radio->videodev, radio); platform_set_drvdata(pdev, radio); radio->v4l2dev.ctrl_handler = &radio->ctrl_handler; v4l2_ctrl_handler_init(&radio->ctrl_handler, 1 + ARRAY_SIZE(s610_ctrls)); /* Set control */ ret = s610_radio_add_new_custom(radio, S610_IDX_CH_SPACING, 0); /*0x01*/ if (ret < 0) goto exit; ret = s610_radio_add_new_custom(radio, S610_IDX_CH_BAND, 0); /*0x02*/ if (ret < 0) goto exit; ret = s610_radio_add_new_custom(radio, S610_IDX_SOFT_STEREO_BLEND, 0); /*0x03*/ if (ret < 0) goto exit; ret = s610_radio_add_new_custom(radio, S610_IDX_SOFT_STEREO_BLEND_COEFF, 0); /*0x04*/ if (ret < 0) goto exit; ret = s610_radio_add_new_custom(radio, S610_IDX_SOFT_MUTE_COEFF, 0); /*0x05*/ if (ret < 0) goto exit; ret = s610_radio_add_new_custom(radio, S610_IDX_RSSI_CURR, V4L2_CTRL_FLAG_VOLATILE); /*0x06*/ if (ret < 0) goto exit; ret = s610_radio_add_new_custom(radio, S610_IDX_SNR_CURR, V4L2_CTRL_FLAG_VOLATILE); /*0x07*/ if (ret < 0) goto exit; ret = s610_radio_add_new_custom(radio, S610_IDX_SEEK_CANCEL, V4L2_CTRL_FLAG_EXECUTE_ON_WRITE); /*0x08*/ if (ret < 0) goto exit; ret = s610_radio_add_new_custom(radio, S610_IDX_SEEK_MODE, 0); /*0x09*/ if (ret < 0) goto exit; ret = s610_radio_add_new_custom(radio, S610_IDX_RDS_ON, 0); /*0x0A*/ if (ret < 0) goto exit; ret = s610_radio_add_new_custom(radio, S610_IDX_IF_COUNT1, 0); /*0x0B*/ if (ret < 0) goto exit; ret = s610_radio_add_new_custom(radio, S610_IDX_IF_COUNT2, 0); /*0x0C*/ if (ret < 0) goto exit; ret = s610_radio_add_new_custom(radio, S610_IDX_RSSI_TH, 0); /*0x0D*/ if (ret < 0) goto exit; ret = s610_radio_add_new_custom(radio, S610_IDX_KERNEL_VER, V4L2_CTRL_FLAG_VOLATILE); /*0x0E*/ if (ret < 0) goto exit; ret = s610_radio_add_new_custom(radio, S610_IDX_SOFT_STEREO_BLEND_REF, V4L2_CTRL_FLAG_EXECUTE_ON_WRITE|V4L2_CTRL_FLAG_VOLATILE); /*0x0F*/ if (ret < 0) goto exit; ctrl = v4l2_ctrl_new_std(&radio->ctrl_handler, &s610_ctrl_ops, V4L2_CID_AUDIO_MUTE, 0, 4, 1, 1); ret = radio->ctrl_handler.error; if (ctrl == NULL && ret) { dev_err(&pdev->dev, "Could not initialize V4L2_CID_AUDIO_MUTE control %d\n", (int)ret); goto exit; } ctrl = v4l2_ctrl_new_std(&radio->ctrl_handler, &s610_ctrl_ops, V4L2_CID_AUDIO_VOLUME, 0, 15, 1, 1); ret = radio->ctrl_handler.error; if (ctrl == NULL && ret) { dev_err(&pdev->dev, "Could not initialize V4L2_CID_AUDIO_VOLUME control %d\n", (int)ret); goto exit; } ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, &s610_ctrl_ops, V4L2_CID_TUNE_DEEMPHASIS, V4L2_DEEMPHASIS_75_uS, 0, V4L2_PREEMPHASIS_75_uS); ret = radio->ctrl_handler.error; if (ctrl == NULL && ret) { dev_err(&pdev->dev, "Could not initialize V4L2_CID_TUNE_DEEMPHASIS control %d\n", (int)ret); goto exit; } /* register video device */ ret = video_register_device(&radio->videodev, VFL_TYPE_RADIO, -1); if (ret < 0) { dev_err(&pdev->dev, "Could not register video device\n"); goto exit; } mutex_init(&radio->lock); s610_core_lock_init(radio->core); /*init_waitqueue_head(&radio->core->rds_read_queue);*/ INIT_DELAYED_WORK(&radio->dwork_sig2, s610_sig2_work); INIT_DELAYED_WORK(&radio->dwork_tune, s610_tune_work); #ifdef RDS_POLLING_ENABLE INIT_DELAYED_WORK(&radio->dwork_rds_poll, s610_rds_poll_work); #endif /*RDS_POLLING_ENABLE*/ #ifdef IDLE_POLLING_ENABLE INIT_DELAYED_WORK(&radio->dwork_idle_poll, s610_idle_poll_work); #endif /*IDLE_POLLING_ENABLE*/ #ifdef ENABLE_RDS_WORK_QUEUE INIT_WORK(&radio->work, s610_rds_work); #endif /*ENABLE_RDS_WORK_QUEUE*/ #ifndef RDS_POLLING_ENABLE #ifdef ENABLE_IF_WORK_QUEUE INIT_WORK(&radio->if_work, s610_if_work); #endif /*ENABLE_IF_WORK_QUEUE*/ #endif /* RDS_POLLING_ENABLE */ /* all aux pll off for WIFI/BT */ radio->rfchip_ver = S620_REV_0; ret = of_property_read_u32(dnode, "fm_iclk_aux", &radio->iclkaux); if (ret) radio->iclkaux = 1; dev_info(radio->dev, "iClk Aux: %d\n", radio->iclkaux); ret = of_property_read_u32(dnode, "num-tcon-freq", &radio->tc_on); if (ret) { radio->tc_on = 0; goto skip_tc_off; } fm_spur_init = devm_kzalloc(&pdev->dev, radio->tc_on * sizeof(*fm_spur_init), GFP_KERNEL); if (!fm_spur_init) { dev_err(radio->dev, "Mem alloc failed for TC ON freq values, TC off\n"); radio->tc_on = 0; goto skip_tc_off; } if (of_property_read_u32_array(dnode, "val-tcon-freq", fm_spur_init, radio->tc_on)) { dev_err(radio->dev, "Getting val-tcon-freq values faild, TC off\n"); radio->tc_on = 0; goto skip_tc_off; } dev_info(radio->dev, "number TC On Freq: %d\n", radio->tc_on); skip_tc_off: ret = of_property_read_u32(dnode, "num-trfon-freq", &radio->trf_on); if (ret) { radio->trf_on = 0; goto skip_trf_off; } fm_spur_trf_init = devm_kzalloc(&pdev->dev, radio->trf_on * sizeof(*fm_spur_trf_init), GFP_KERNEL); if (!fm_spur_trf_init) { dev_err(radio->dev, "Mem alloc failed for TRF ON freq values, TRF off\n"); radio->trf_on = 0; goto skip_trf_off; } if (of_property_read_u32_array(dnode, "val-trfon-freq", fm_spur_trf_init, radio->trf_on)) { dev_err(radio->dev, "Getting val-trfon-freq values faild, TRF off\n"); radio->trf_on = 0; goto skip_trf_off; } dev_info(radio->dev, "number TRF On Freq: %d\n", radio->trf_on); skip_trf_off: ret = of_property_read_u32(dnode, "num-dual-clkon-freq", &radio->dual_clk_on); if (ret) { radio->dual_clk_on = 0; goto skip_dual_clk_off; } fm_dual_clk_init = devm_kzalloc(&pdev->dev, radio->dual_clk_on * sizeof(*fm_dual_clk_init), GFP_KERNEL); if (!fm_dual_clk_init) { dev_err(radio->dev, "Mem alloc failed for DUAL CLK ON freq values, DUAL CLK off\n"); radio->dual_clk_on = 0; goto skip_dual_clk_off; } if (of_property_read_u32_array(dnode, "val-dual-clkon-freq", fm_dual_clk_init, radio->dual_clk_on)) { dev_err(radio->dev, "Getting val-dual-clkon-freq values faild, DUAL CLK off\n"); radio->dual_clk_on = 0; goto skip_dual_clk_off; } dev_info(radio->dev, "number DUAL CLK On Freq: %d\n", radio->dual_clk_on); skip_dual_clk_off: radio->vol_num = FM_RX_VOLUME_GAIN_STEP; radio->vol_level_mod = vol_level_init; ret = of_property_read_u32(dnode, "num-volume-level", &radio->vol_num); if (ret) { goto skip_vol_sel; } radio->vol_level_tmp = devm_kzalloc(&pdev->dev, radio->vol_num * sizeof(u32), GFP_KERNEL); if (!radio->vol_level_tmp) { dev_err(radio->dev, "Mem alloc failed for Volume level values, Volume Level default setting\n"); goto skip_vol_sel; } if (of_property_read_u32_array(dnode, "val-vol-level", radio->vol_level_tmp, radio->vol_num)) { dev_err(radio->dev, "Getting val-vol-level values faild, Volume Level default stting\n"); kfree(radio->vol_level_tmp); radio->vol_num = FM_RX_VOLUME_GAIN_STEP; goto skip_vol_sel; } radio->vol_level_mod = radio->vol_level_tmp; skip_vol_sel: dev_info(radio->dev, "volume select num: %d\n", radio->vol_num); ret = of_property_read_u32(dnode, "vol_3db_on", &radio->vol_3db_att); if (ret) radio->vol_3db_att = 0; dev_info(radio->dev, "volume -3dB: %d\n", radio->vol_3db_att); ret = of_property_read_u32(dnode, "rssi_est_on", &radio->rssi_est_on); if (ret) radio->rssi_est_on = 0; dev_info(radio->dev, "rssi_est_on: %d\n", radio->rssi_est_on); ret = of_property_read_u32(dnode, "sw_mute_weak", &radio->sw_mute_weak); if (ret) radio->sw_mute_weak = 0; dev_info(radio->dev, "sw_mute_weak: %d\n", radio->sw_mute_weak); ret = of_property_read_u32(dnode, "without_elna", &radio->without_elna); if (ret) radio->without_elna = 0; dev_info(radio->dev, "without eLNA: %d\n", radio->without_elna); ret = of_property_read_u16(dnode, "rssi_adjust", &radio->rssi_adjust); if (ret) radio->rssi_adjust= 0; dev_info(radio->dev, "rssi adjust: %d\n", radio->rssi_adjust); wake_lock_init(&radio->wakelock, WAKE_LOCK_SUSPEND, "fm_wake"); wake_lock_init(&radio->rdswakelock, WAKE_LOCK_SUSPEND, "fm_rdswake"); dev_info(&pdev->dev, "end FM probe.\n"); return 0; exit: v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); alloc_err4: v4l2_device_unregister(&radio->v4l2dev); alloc_err3: pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); fm_clk_disable(radio); fm_clk_unprepare(radio); alloc_err2: kfree(radio->low); alloc_err1: kfree(radio->core); alloc_err0: kfree(radio); return ret; } static int s610_radio_remove(struct platform_device *pdev) { struct s610_radio *radio = platform_get_drvdata(pdev); if (radio) { fm_clk_disable(radio); fm_clk_unprepare(radio); fm_clk_put(radio); pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); video_unregister_device(&radio->videodev); v4l2_device_unregister(&radio->v4l2dev); wake_lock_destroy(&radio->wakelock); wake_lock_destroy(&radio->rdswakelock); kfree(radio->vol_level_tmp); kfree(radio->low); kfree(radio->core); kfree(radio); } return 0; } static int fm_clk_get(struct s610_radio *radio) { struct device *dev = radio->dev; struct clk *clk; int clk_count; int ret, i; clk_count = of_property_count_strings(dev->of_node, "clock-names"); if (IS_ERR_VALUE((unsigned long)clk_count)) { dev_err(dev, "invalid clk list in %s node", dev->of_node->name); return -EINVAL; } radio->clk_ids = (const char **)devm_kmalloc(dev, (clk_count + 1) * sizeof(const char *), GFP_KERNEL); if (!radio->clk_ids) { dev_err(dev, "failed to alloc for clock ids"); return -ENOMEM; } for (i = 0; i < clk_count; i++) { ret = of_property_read_string_index(dev->of_node, "clock-names", i, &radio->clk_ids[i]); if (ret) { dev_err(dev, "failed to read clocks name %d from %s node\n", i, dev->of_node->name); return ret; } } radio->clk_ids[clk_count] = NULL; radio->clocks = (struct clk **) devm_kmalloc(dev, clk_count * sizeof(struct clk *), GFP_KERNEL); if (!radio->clocks) { dev_err(dev, "%s: couldn't alloc\n", __func__); return -ENOMEM; } for (i = 0; radio->clk_ids[i] != NULL; i++) { clk = devm_clk_get(dev, radio->clk_ids[i]); if (IS_ERR_OR_NULL(clk)) goto err; radio->clocks[i] = clk; } radio->clocks[i] = NULL; return 0; err: dev_err(dev, "couldn't get %s clock\n", radio->clk_ids[i]); return -EINVAL; } static int fm_clk_prepare(struct s610_radio *radio) { int i; int ret; for (i = 0; radio->clocks[i] != NULL; i++) { ret = clk_prepare(radio->clocks[i]); if (ret) goto err; } return 0; err: dev_err(radio->dev, "couldn't prepare clock[%d]\n", i); /* roll back */ for (i = i - 1; i >= 0; i--) clk_unprepare(radio->clocks[i]); return ret; } static int fm_clk_enable(struct s610_radio *radio) { int i; int ret; for (i = 0; radio->clocks[i] != NULL; i++) { ret = clk_enable(radio->clocks[i]); if (ret) goto err; dev_info(radio->dev, "clock %s: %lu\n", radio->clk_ids[i], clk_get_rate(radio->clocks[i])); if (!strcmp(radio->clk_ids[i], "clk_aud_fm")) { if (clk_get_rate(radio->clocks[i]) != 40000) { ret = clk_set_rate(radio->clocks[i], 40000); if (IS_ERR_VALUE((unsigned long)ret)) dev_info(radio->dev, "setting clock clk_aud_fm to 40KHz is failed: %lu\n", ret); dev_info(radio->dev, "FM clock clk_aud_fm: %lu\n", clk_get_rate(radio->clocks[i])); } } } return 0; err: dev_err(radio->dev, "couldn't enable clock[%d]\n", i); /* roll back */ for (i = i - 1; i >= 0; i--) clk_disable(radio->clocks[i]); return ret; } static void fm_clk_unprepare(struct s610_radio *radio) { int i; for (i = 0; radio->clocks[i] != NULL; i++) clk_unprepare(radio->clocks[i]); } static void fm_clk_disable(struct s610_radio *radio) { int i; for (i = 0; radio->clocks[i] != NULL; i++) clk_disable(radio->clocks[i]); } static void fm_clk_put(struct s610_radio *radio) { int i; for (i = 0; radio->clocks[i] != NULL; i++) clk_put(radio->clocks[i]); } #ifdef CONFIG_PM static int fm_radio_runtime_suspend(struct device *dev) { struct s610_radio *radio = dev_get_drvdata(dev); FUNC_ENTRY(radio); fm_clk_disable(radio); return 0; } static int fm_radio_runtime_resume(struct device *dev) { struct s610_radio *radio = dev_get_drvdata(dev); FUNC_ENTRY(radio); fm_clk_enable(radio); return 0; } #endif #ifdef CONFIG_PM_SLEEP static int fm_radio_suspend(struct device *dev) { // struct s610_radio *radio = dev_get_drvdata(dev); // FUNC_ENTRY(radio); return 0; } static int fm_radio_resume(struct device *dev) { // struct s610_radio *radio = dev_get_drvdata(dev); // FUNC_ENTRY(radio); return 0; } #endif /* CONFIG_PM_SLEEP */ static const struct dev_pm_ops fm_radio_dev_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(fm_radio_suspend, fm_radio_resume) SET_RUNTIME_PM_OPS(fm_radio_runtime_suspend, fm_radio_runtime_resume, NULL) }; #define DEV_PM_OPS (&fm_radio_dev_pm_ops) static struct platform_driver s610_radio_driver = { .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = exynos_fm_of_match, .pm = DEV_PM_OPS, }, .probe = s610_radio_probe, .remove = s610_radio_remove, }; static int __init init_s610_radio(void) { platform_driver_register(&s610_radio_driver); return 0; } static void __exit exit_s610_radio(void) { platform_driver_unregister(&s610_radio_driver); } late_initcall(init_s610_radio); module_exit(exit_s610_radio); MODULE_AUTHOR("Youngjoon Chung, "); MODULE_DESCRIPTION("Driver for S610 FM Radio in Exynos9610"); MODULE_LICENSE("GPL");