| /* |
| * Copyright (C) 2017 Amlogic, Inc. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| * |
| * Description: |
| */ |
| /* |
| * AMLOGIC DVB frontend driver. |
| */ |
| |
| #include <linux/version.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/wait.h> |
| #include <linux/string.h> |
| #include <linux/interrupt.h> |
| #include <linux/fs.h> |
| #include <linux/cdev.h> |
| #include <linux/device.h> |
| #include <linux/spinlock.h> |
| #include <linux/fcntl.h> |
| #include <asm/irq.h> |
| #include <linux/uaccess.h> |
| #include <linux/poll.h> |
| #include <linux/delay.h> |
| #include <linux/platform_device.h> |
| #include <linux/gpio/consumer.h> |
| #include <linux/gpio.h> |
| #include <linux/dma-contiguous.h> |
| #include <linux/of_gpio.h> |
| /*add for gpio api by hualing.chen*/ |
| #include <gpiolib.h> |
| |
| #include "aml_fe.h" |
| |
| #ifdef pr_dbg |
| #undef pr_dbg |
| #endif |
| |
| #define pr_dbg(fmt, args ...) \ |
| pr_info("FE: " fmt, ## args) |
| #define pr_error(fmt, args ...) pr_err("FE: " fmt, ## args) |
| #define pr_inf(fmt, args ...) pr_info("FE: " fmt, ## args) |
| |
| static DEFINE_SPINLOCK(lock); |
| static struct aml_fe_drv *tuner_drv_list; |
| static struct aml_fe_drv *atv_demod_drv_list; |
| static struct aml_fe_drv *dtv_demod_drv_list; |
| static struct aml_fe_man fe_man; |
| static long aml_fe_suspended; |
| |
| static int aml_fe_set_sys(struct dvb_frontend *dev, |
| enum fe_delivery_system sys); |
| |
| static struct aml_fe_drv **aml_get_fe_drv_list(enum aml_fe_dev_type_t type) |
| { |
| switch (type) { |
| case AM_DEV_TUNER: |
| return &tuner_drv_list; |
| case AM_DEV_ATV_DEMOD: |
| return &atv_demod_drv_list; |
| case AM_DEV_DTV_DEMOD: |
| return &dtv_demod_drv_list; |
| default: |
| return NULL; |
| } |
| } |
| |
| |
| |
| int amlogic_gpio_direction_output(unsigned int pin, int value, |
| const char *owner) |
| { |
| gpio_direction_output(pin, value); |
| return 0; |
| } |
| EXPORT_SYMBOL(amlogic_gpio_direction_output); |
| int amlogic_gpio_request(unsigned int pin, const char *label) |
| { |
| return 0; |
| } |
| EXPORT_SYMBOL(amlogic_gpio_request); |
| int aml_register_fe_drv(enum aml_fe_dev_type_t type, struct aml_fe_drv *drv) |
| { |
| if (drv) { |
| struct aml_fe_drv **list = aml_get_fe_drv_list(type); |
| unsigned long flags; |
| |
| spin_lock_irqsave(&lock, flags); |
| |
| drv->next = *list; |
| *list = drv; |
| |
| drv->ref = 0; |
| |
| spin_unlock_irqrestore(&lock, flags); |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(aml_register_fe_drv); |
| |
| int aml_unregister_fe_drv(enum aml_fe_dev_type_t type, struct aml_fe_drv *drv) |
| { |
| int ret = 0; |
| |
| if (drv) { |
| struct aml_fe_drv *pdrv, *pprev; |
| struct aml_fe_drv **list = aml_get_fe_drv_list(type); |
| unsigned long flags; |
| |
| spin_lock_irqsave(&lock, flags); |
| |
| if (!drv->ref) { |
| for (pprev = NULL, pdrv = *list; |
| pdrv; pprev = pdrv, pdrv = pdrv->next) { |
| if (pdrv == drv) { |
| if (pprev) |
| pprev->next = pdrv->next; |
| else |
| *list = pdrv->next; |
| break; |
| } |
| } |
| } else { |
| pr_error("fe driver %d is inused\n", drv->id); |
| ret = -1; |
| } |
| |
| spin_unlock_irqrestore(&lock, flags); |
| } |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(aml_unregister_fe_drv); |
| |
| static int aml_fe_support_sys(struct aml_fe *fe, |
| enum fe_delivery_system sys) |
| { |
| if (fe->dtv_demod |
| && fe->dtv_demod->drv |
| && fe->dtv_demod->drv->support) { |
| if (!fe->dtv_demod->drv->support(fe->dtv_demod, sys)) |
| return 0; |
| } |
| |
| if (fe->atv_demod |
| && fe->atv_demod->drv |
| && fe->atv_demod->drv->support) { |
| if (!fe->atv_demod->drv->support(fe->atv_demod, sys)) |
| return 0; |
| } |
| |
| if (fe->tuner |
| && fe->tuner->drv |
| && fe->tuner->drv->support) { |
| if (!fe->tuner->drv->support(fe->tuner, sys)) |
| return 0; |
| } |
| |
| return 1; |
| } |
| /* |
| *#define DTV_START_BLIND_SCAN 71 |
| *#define DTV_CANCEL_BLIND_SCAN 72 |
| *#define DTV_BLIND_SCAN_MIN_FRE 73 |
| *#define DTV_BLIND_SCAN_MAX_FRE 74 |
| *#define DTV_BLIND_SCAN_MIN_SRATE 75 |
| *#define DTV_BLIND_SCAN_MAX_SRATE 76 |
| *#define DTV_BLIND_SCAN_FRE_RANGE 77 |
| *#define DTV_BLIND_SCAN_FRE_STEP 78 |
| *#define DTV_BLIND_SCAN_TIMEOUT 79 |
| */ |
| static int aml_fe_blind_cmd(struct dvb_frontend *dev, |
| struct dtv_property *tvp) |
| { |
| struct aml_fe *fe; |
| int ret = 0; |
| |
| fe = dev->demodulator_priv; |
| pr_error("fe blind cmd into cmd:[%d]\n", tvp->cmd); |
| switch (tvp->cmd) { |
| case DTV_START_BLIND_SCAN: |
| if (fe->dtv_demod |
| && fe->dtv_demod->drv |
| && fe->dtv_demod->drv->start_blind_scan) { |
| ret = fe->dtv_demod->drv->start_blind_scan( |
| fe->dtv_demod); |
| } else { |
| pr_error("fe dtv_demod not surport blind start\n"); |
| } |
| break; |
| case DTV_CANCEL_BLIND_SCAN: |
| if (fe->dtv_demod |
| && fe->dtv_demod->drv |
| && fe->dtv_demod->drv->stop_blind_scan) { |
| ret = fe->dtv_demod->drv->stop_blind_scan( |
| fe->dtv_demod); |
| } else { |
| pr_error("fe dtv_demod not surport blind stop\n"); |
| } |
| break; |
| case DTV_BLIND_SCAN_MIN_FRE: |
| fe->blind_scan_para.minfrequency = tvp->u.data; |
| break; |
| case DTV_BLIND_SCAN_MAX_FRE: |
| fe->blind_scan_para.maxfrequency = tvp->u.data; |
| break; |
| case DTV_BLIND_SCAN_MIN_SRATE: |
| fe->blind_scan_para.minSymbolRate = tvp->u.data; |
| break; |
| case DTV_BLIND_SCAN_MAX_SRATE: |
| fe->blind_scan_para.maxSymbolRate = tvp->u.data; |
| break; |
| case DTV_BLIND_SCAN_FRE_RANGE: |
| fe->blind_scan_para.frequencyRange = tvp->u.data; |
| break; |
| case DTV_BLIND_SCAN_FRE_STEP: |
| fe->blind_scan_para.frequencyStep = tvp->u.data; |
| break; |
| case DTV_BLIND_SCAN_TIMEOUT: |
| fe->blind_scan_para.timeout = tvp->u.data; |
| break; |
| default: |
| ret = 0; |
| break; |
| } |
| return ret; |
| } |
| |
| static int aml_fe_set_property(struct dvb_frontend *dev, |
| struct dtv_property *tvp) |
| { |
| struct aml_fe *fe; |
| int r = 0; |
| |
| fe = dev->demodulator_priv; |
| |
| if (tvp->cmd == DTV_DELIVERY_SYSTEM) { |
| enum fe_delivery_system sys = tvp->u.data; |
| |
| pr_error("fe aml_fe_set_property %d\n", sys); |
| r = aml_fe_set_sys(dev, sys); |
| if (r < 0) |
| return r; |
| } |
| |
| if (tvp->cmd == DTV_DELIVERY_SUB_SYSTEM) { |
| int sub_sys = tvp->u.data; |
| |
| pr_error("fe aml_fe_set_property sub_sys: %d\n", sub_sys); |
| fe->sub_sys = sub_sys; |
| r = 0; |
| } |
| pr_error("fe aml_fe_set_property -tvp->cmd[%d]\n", tvp->cmd); |
| switch (tvp->cmd) { |
| case DTV_START_BLIND_SCAN: |
| case DTV_CANCEL_BLIND_SCAN: |
| case DTV_BLIND_SCAN_MIN_FRE: |
| case DTV_BLIND_SCAN_MAX_FRE: |
| case DTV_BLIND_SCAN_MIN_SRATE: |
| case DTV_BLIND_SCAN_MAX_SRATE: |
| case DTV_BLIND_SCAN_FRE_RANGE: |
| case DTV_BLIND_SCAN_FRE_STEP: |
| case DTV_BLIND_SCAN_TIMEOUT: |
| r = aml_fe_blind_cmd(dev, tvp); |
| if (r < 0) |
| return r; |
| default: |
| break; |
| } |
| |
| if (fe->set_property) { |
| pr_error("fe fe->set_property -0\n"); |
| return fe->set_property(dev, tvp); |
| } |
| pr_error("fe aml_fe_set_property -2\n"); |
| return r; |
| } |
| |
| static int aml_fe_get_property(struct dvb_frontend *dev, |
| struct dtv_property *tvp) |
| { |
| struct aml_fe *fe; |
| int r = 0; |
| |
| fe = dev->demodulator_priv; |
| |
| if (tvp->cmd == DTV_TS_INPUT) |
| tvp->u.data = fe->ts; |
| if (tvp->cmd == DTV_DELIVERY_SUB_SYSTEM) { |
| tvp->u.data = fe->sub_sys; |
| pr_error("fe aml_fe_get_property sub_sys: %d\n", fe->sub_sys); |
| r = 0; |
| } |
| if (fe->get_property) |
| return fe->get_property(dev, tvp); |
| |
| return r; |
| } |
| |
| static int aml_fe_set_sys(struct dvb_frontend *dev, |
| enum fe_delivery_system sys) |
| { |
| struct aml_fe *fe = dev->demodulator_priv; |
| unsigned long flags; |
| int ret = -1; |
| |
| if (fe->sys == sys) { |
| pr_dbg("[%s]:the mode is not change!!!!\n", __func__); |
| return 0; |
| } |
| /*set dvb-t or dvb-t2 |
| * if dvb-t or t2 is set |
| * we only set sys value, not init sys |
| */ |
| if (fe->sys != SYS_UNDEFINED) { |
| pr_dbg("release system %d\n", fe->sys); |
| |
| if (fe->dtv_demod |
| && fe->dtv_demod->drv |
| && fe->dtv_demod->drv->release_sys) |
| fe->dtv_demod->drv->release_sys(fe->dtv_demod, fe->sys); |
| if (fe->atv_demod |
| && fe->atv_demod->drv |
| && fe->atv_demod->drv->release_sys) |
| fe->atv_demod->drv->release_sys(fe->atv_demod, fe->sys); |
| if (fe->tuner |
| && fe->tuner->drv |
| && fe->tuner->drv->release_sys) |
| fe->tuner->drv->release_sys(fe->tuner, fe->sys); |
| |
| fe->set_property = NULL; |
| fe->get_property = NULL; |
| |
| fe->sys = SYS_UNDEFINED; |
| } |
| |
| if (sys == SYS_UNDEFINED) |
| return 0; |
| |
| if (!aml_fe_support_sys(fe, sys)) { |
| int i; |
| |
| spin_lock_irqsave(&lock, flags); |
| for (i = 0; i < FE_DEV_COUNT; i++) { |
| if (aml_fe_support_sys(&fe_man.fe[i], sys) |
| && (fe_man.fe[i].dev_id == fe->dev_id)) |
| break; |
| } |
| spin_unlock_irqrestore(&lock, flags); |
| |
| if (i >= FE_DEV_COUNT) { |
| pr_error("do not support delivery system %d\n", sys); |
| return -1; |
| } |
| |
| fe = &fe_man.fe[i]; |
| dev->demodulator_priv = fe; |
| } |
| |
| spin_lock_irqsave(&fe->slock, flags); |
| |
| memset(&fe->fe->ops.tuner_ops, 0, sizeof(fe->fe->ops.tuner_ops)); |
| memset(&fe->fe->ops.analog_ops, 0, sizeof(fe->fe->ops.analog_ops)); |
| memset(&fe->fe->ops.info, 0, sizeof(fe->fe->ops.info)); |
| fe->fe->ops.release = NULL; |
| fe->fe->ops.release_sec = NULL; |
| fe->fe->ops.init = NULL; |
| fe->fe->ops.sleep = NULL; |
| fe->fe->ops.write = NULL; |
| fe->fe->ops.tune = NULL; |
| fe->fe->ops.get_frontend_algo = NULL; |
| fe->fe->ops.set_frontend = NULL; |
| fe->fe->ops.get_tune_settings = NULL; |
| fe->fe->ops.get_frontend = NULL; |
| fe->fe->ops.read_status = NULL; |
| fe->fe->ops.read_ber = NULL; |
| fe->fe->ops.read_signal_strength = NULL; |
| fe->fe->ops.read_snr = NULL; |
| fe->fe->ops.read_ucblocks = NULL; |
| fe->fe->ops.diseqc_reset_overload = NULL; |
| fe->fe->ops.diseqc_send_master_cmd = NULL; |
| fe->fe->ops.diseqc_recv_slave_reply = NULL; |
| fe->fe->ops.diseqc_send_burst = NULL; |
| fe->fe->ops.set_tone = NULL; |
| fe->fe->ops.set_voltage = NULL; |
| fe->fe->ops.enable_high_lnb_voltage = NULL; |
| fe->fe->ops.dishnetwork_send_legacy_command = NULL; |
| fe->fe->ops.i2c_gate_ctrl = NULL; |
| fe->fe->ops.ts_bus_ctrl = NULL; |
| fe->fe->ops.search = NULL; |
| fe->fe->ops.set_property = NULL; |
| fe->fe->ops.get_property = NULL; |
| |
| if (fe->tuner |
| && fe->tuner->drv |
| && fe->tuner->drv->get_ops) |
| fe->tuner->drv->get_ops(fe->tuner, sys, &fe->fe->ops); |
| |
| if (fe->atv_demod |
| && fe->atv_demod->drv |
| && fe->atv_demod->drv->get_ops) |
| fe->atv_demod->drv->get_ops(fe->atv_demod, sys, &fe->fe->ops); |
| |
| if (fe->dtv_demod |
| && fe->dtv_demod->drv |
| && fe->dtv_demod->drv->get_ops) |
| fe->dtv_demod->drv->get_ops(fe->dtv_demod, sys, &fe->fe->ops); |
| |
| spin_unlock_irqrestore(&fe->slock, flags); |
| |
| pr_dbg("init system %d\n", sys); |
| |
| if (fe->dtv_demod |
| && fe->dtv_demod->drv |
| && fe->dtv_demod->drv->init_sys) |
| ret = fe->dtv_demod->drv->init_sys(fe->dtv_demod, sys); |
| if (fe->atv_demod |
| && fe->atv_demod->drv |
| && fe->atv_demod->drv->init_sys) |
| ret = fe->atv_demod->drv->init_sys(fe->atv_demod, sys); |
| if (fe->tuner |
| && fe->tuner->drv |
| && fe->tuner->drv->init_sys) |
| ret = fe->tuner->drv->init_sys(fe->tuner, sys); |
| |
| if (ret != 0) { |
| pr_error("init system, %d fail, ret %d\n", sys, ret); |
| goto end; |
| } |
| |
| fe->set_property = fe->fe->ops.set_property; |
| fe->get_property = fe->fe->ops.get_property; |
| |
| strcpy(fe->fe->ops.info.name, "amlogic dvb frontend"); |
| fe->sys = sys; |
| pr_dbg("set mode ok\n"); |
| |
| end: |
| fe->fe->ops.set_property = aml_fe_set_property; |
| fe->fe->ops.get_property = aml_fe_get_property; |
| |
| return 0; |
| } |
| |
| static const char *aml_fe_dev_type_str(struct aml_fe_dev *dev) |
| { |
| switch (dev->type) { |
| case AM_DEV_TUNER: |
| return "tuner"; |
| case AM_DEV_ATV_DEMOD: |
| return "atv_demod"; |
| case AM_DEV_DTV_DEMOD: |
| return "dtv_demod"; |
| } |
| |
| return ""; |
| } |
| |
| static void aml_fe_property_name(struct aml_fe_dev *dev, const char *name, |
| char *buf) |
| { |
| const char *tstr; |
| |
| tstr = aml_fe_dev_type_str(dev); |
| |
| if (name) |
| sprintf(buf, "%s%d_%s", tstr, dev->dev_id, name); |
| else |
| sprintf(buf, "%s%d", tstr, dev->dev_id); |
| } |
| |
| int aml_fe_of_property_string(struct aml_fe_dev *dev, |
| const char *name, const char **str) |
| { |
| struct platform_device *pdev = dev->man->pdev; |
| //char buf[128]; |
| int r; |
| |
| //aml_fe_property_name(dev, name, buf); |
| pr_error("start find resource: \"%s\" --\n", name); |
| r = of_property_read_string(pdev->dev.of_node, name, str); |
| if (r) |
| pr_error("cannot find resource: \"%s\"\n", name); |
| |
| return r; |
| } |
| EXPORT_SYMBOL(aml_fe_of_property_string); |
| |
| int aml_fe_of_property_u32(struct aml_fe_dev *dev, |
| const char *name, u32 *v) |
| { |
| struct platform_device *pdev = dev->man->pdev; |
| //char buf[128]; |
| int r; |
| |
| //aml_fe_property_name(dev, name, buf); |
| r = of_property_read_u32(pdev->dev.of_node, name, v); |
| if (r) |
| pr_error("cannot find resource \"%s\"\n", name); |
| |
| return r; |
| } |
| EXPORT_SYMBOL(aml_fe_of_property_u32); |
| |
| static int aml_fe_dev_init(struct aml_fe_man *man, |
| enum aml_fe_dev_type_t type, |
| struct aml_fe_dev *dev, |
| int id) |
| { |
| struct aml_fe_drv **list = aml_get_fe_drv_list(type); |
| struct aml_fe_drv *drv; |
| unsigned long flags; |
| char buf[128]; |
| const char *str; |
| char *name = NULL; |
| u32 value; |
| int ret; |
| struct device_node *node; |
| |
| dev->man = man; |
| dev->dev_id = id; |
| dev->type = type; |
| dev->drv = NULL; |
| dev->fe = NULL; |
| dev->priv_data = NULL; |
| |
| memset(buf, 0, 128); |
| name = NULL; |
| aml_fe_property_name(dev, name, buf); |
| pr_dbg("get string: %s\n", buf); |
| ret = aml_fe_of_property_string(dev, buf, &str); |
| if (ret) { |
| pr_dbg("get string: %s error\n", buf); |
| return 0; |
| } |
| |
| |
| spin_lock_irqsave(&lock, flags); |
| |
| for (drv = *list; drv; drv = drv->next) |
| if (!strcmp(drv->name, str)) |
| break; |
| |
| if (dev->drv != drv) { |
| if (dev->drv) { |
| dev->drv->ref--; |
| if (dev->drv->owner) |
| module_put(dev->drv->owner); |
| } |
| if (drv) { |
| drv->ref++; |
| if (drv->owner) |
| try_module_get(drv->owner); |
| } |
| dev->drv = drv; |
| } |
| |
| spin_unlock_irqrestore(&lock, flags); |
| |
| if (drv) { |
| pr_inf("found driver: %s\n", str); |
| } else { |
| pr_err("cannot find driver: %s\n", str); |
| return -1; |
| } |
| /*get i2c adap and i2c addr*/ |
| memset(buf, 0, 128); |
| name = "i2c_adap"; |
| aml_fe_property_name(dev, name, buf); |
| pr_dbg("get u32: %s\n", buf); |
| //ret = aml_fe_of_property_u32(dev, buf, &value); |
| node = of_parse_phandle(dev->man->pdev->dev.of_node, buf, 0); |
| if (node) { |
| dev->i2c_adap = of_find_i2c_adapter_by_node(node); |
| pr_inf("%s:[%p]\n", buf, dev->i2c_adap); |
| of_node_put(node); |
| } else { |
| dev->i2c_adap_id = -1; |
| pr_error("cannot find resource \"%s\"\n", buf); |
| } |
| memset(buf, 0, 128); |
| name = "i2c_addr"; |
| aml_fe_property_name(dev, name, buf); |
| pr_dbg("get u32: %s\n", buf); |
| ret = aml_fe_of_property_u32(dev, buf, &value); |
| if (!ret) { |
| dev->i2c_addr = value; |
| pr_inf("%s: %d\n", buf, dev->i2c_addr); |
| } else { |
| dev->i2c_addr = -1; |
| pr_error("cannot find resource \"%s\"\n", buf); |
| } |
| /*get i2c reset and reset value*/ |
| memset(buf, 0, 128); |
| name = "reset_gpio"; |
| aml_fe_property_name(dev, name, buf); |
| pr_dbg("get string: %s\n", buf); |
| ret = aml_fe_of_property_string(dev, buf, &str); |
| if (!ret) { |
| dev->reset_gpio = |
| of_get_named_gpio_flags(dev->man->pdev->dev.of_node, |
| buf, 0, NULL); |
| pr_inf("%s: %s\n", buf, str); |
| } else { |
| dev->reset_gpio = -1; |
| pr_error("cannot find resource \"%s\"\n", buf); |
| } |
| memset(buf, 0, 128); |
| name = "reset_value"; |
| aml_fe_property_name(dev, name, buf); |
| pr_dbg("get u32: %s\n", buf); |
| ret = aml_fe_of_property_u32(dev, buf, &value); |
| if (!ret) { |
| dev->reset_value = value; |
| pr_inf("%s: %d\n", buf, dev->reset_value); |
| } else { |
| dev->reset_value = -1; |
| } |
| |
| if (dev->drv && dev->drv->init) { |
| int ret; |
| |
| ret = dev->drv->init(dev); |
| if (ret != 0) { |
| dev->drv = NULL; |
| pr_error("[aml_fe..]%s error.\n", __func__); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int aml_fe_dev_release(struct aml_fe_dev *dev) |
| { |
| if (dev->drv) { |
| if (dev->drv->owner) |
| module_put(dev->drv->owner); |
| dev->drv->ref--; |
| if (dev->drv->release) |
| dev->drv->release(dev); |
| } |
| |
| dev->drv = NULL; |
| return 0; |
| } |
| |
| static void aml_fe_man_run(struct aml_fe *fe) |
| { |
| if (fe->init) |
| return; |
| |
| if (fe->tuner && fe->tuner->drv) |
| fe->init = 1; |
| |
| if (fe->atv_demod && fe->atv_demod->drv) |
| fe->init = 1; |
| |
| if (fe->dtv_demod && fe->dtv_demod->drv) |
| fe->init = 1; |
| |
| if (fe->init) { |
| struct aml_dvb *dvb = fe->man->dvb; |
| int reg = 1; |
| int ret; |
| int id; |
| |
| spin_lock_init(&fe->slock); |
| |
| fe->sys = SYS_UNDEFINED; |
| |
| pr_dbg("fe: %p\n", fe); |
| |
| for (id = 0; id < FE_DEV_COUNT; id++) { |
| struct aml_fe *prev_fe = &fe_man.fe[id]; |
| |
| if (prev_fe == fe) |
| continue; |
| if (prev_fe->init && (prev_fe->dev_id == fe->dev_id)) { |
| reg = 0; |
| break; |
| } |
| } |
| |
| fe->fe = &fe_man.dev[fe->dev_id]; |
| if (reg) { |
| fe->fe->demodulator_priv = fe; |
| fe->fe->ops.set_property = aml_fe_set_property; |
| fe->fe->ops.get_property = aml_fe_set_property; |
| } |
| |
| if (fe->tuner) |
| fe->tuner->fe = fe; |
| if (fe->atv_demod) |
| fe->atv_demod->fe = fe; |
| if (fe->dtv_demod) |
| fe->dtv_demod->fe = fe; |
| |
| ret = dvb_register_frontend(&dvb->dvb_adapter, fe->fe); |
| if (ret) { |
| pr_error("register fe%d failed\n", fe->dev_id); |
| return; |
| } |
| } |
| } |
| |
| static void fe_property_name(struct aml_fe *fe, const char *name, |
| char *buf) |
| { |
| if (name) |
| sprintf(buf, "fe%d_%s", fe->dev_id, name); |
| else |
| sprintf(buf, "fe%d", fe->dev_id); |
| } |
| |
| static int fe_of_property_u32(struct aml_fe *fe, |
| const char *name, u32 *v) |
| { |
| struct platform_device *pdev = fe->man->pdev; |
| char buf[128]; |
| int r; |
| |
| fe_property_name(fe, name, buf); |
| r = of_property_read_u32(pdev->dev.of_node, buf, v); |
| if (r) |
| pr_error("cannot find resource \"%s\"\n", buf); |
| |
| return r; |
| } |
| |
| static int aml_fe_man_init(struct aml_fe_man *man, struct aml_fe *fe, int id) |
| { |
| u32 value; |
| int ret; |
| |
| fe->sys = SYS_UNDEFINED; |
| fe->man = man; |
| fe->dev_id = id; |
| fe->init = 0; |
| fe->ts = AM_TS_SRC_TS0; |
| fe->work_running = 0; |
| fe->work_q = NULL; |
| fe->tuner = NULL; |
| fe->atv_demod = NULL; |
| fe->dtv_demod = NULL; |
| fe->do_work = NULL; |
| fe->get_property = NULL; |
| fe->set_property = NULL; |
| |
| init_waitqueue_head(&fe->wait_q); |
| spin_lock_init(&fe->slock); |
| |
| ret = fe_of_property_u32(fe, "tuner", &value); |
| if (!ret) { |
| id = value; |
| |
| if ((id < 0) || (id >= FE_DEV_COUNT) || !fe_man.tuner[id].drv) { |
| pr_error("invalid tuner device id %d\n", id); |
| return -1; |
| } |
| |
| fe->tuner = &fe_man.tuner[id]; |
| fe_man.tuner[id].fe = fe; |
| } |
| |
| ret = fe_of_property_u32(fe, "atv_demod", &value); |
| if (!ret) { |
| id = value; |
| |
| if ((id < 0) || |
| (id >= FE_DEV_COUNT) || |
| !fe_man.atv_demod[id].drv) { |
| pr_error("invalid ATV demod device id %d\n", id); |
| return -1; |
| } |
| |
| fe->atv_demod = &fe_man.atv_demod[id]; |
| fe_man.atv_demod[id].fe = fe; |
| } |
| |
| ret = fe_of_property_u32(fe, "dtv_demod", &value); |
| if (!ret) { |
| id = value; |
| |
| if ((id < 0) || |
| (id >= FE_DEV_COUNT) || |
| !fe_man.dtv_demod[id].drv) { |
| pr_error("invalid DTV demod device id %d\n", id); |
| return -1; |
| } |
| |
| fe->dtv_demod = &fe_man.dtv_demod[id]; |
| fe_man.dtv_demod[id].fe = fe; |
| } |
| |
| ret = fe_of_property_u32(fe, "ts", &value); |
| if (!ret) { |
| enum aml_ts_source_t ts = AM_TS_SRC_TS0; |
| |
| switch (value) { |
| case 0: |
| ts = AM_TS_SRC_TS0; |
| break; |
| case 1: |
| ts = AM_TS_SRC_TS1; |
| break; |
| case 2: |
| ts = AM_TS_SRC_TS2; |
| break; |
| default: |
| break; |
| } |
| |
| fe->ts = ts; |
| } |
| |
| ret = fe_of_property_u32(fe, "dev", &value); |
| if (!ret) { |
| id = value; |
| |
| if ((id >= 0) && (id < FE_DEV_COUNT)) |
| fe->dev_id = id; |
| else |
| fe->dev_id = 0; |
| } |
| |
| aml_fe_man_run(fe); |
| |
| return 0; |
| } |
| |
| static int aml_fe_man_release(struct aml_fe *fe) |
| { |
| if (fe->init) { |
| aml_fe_cancel_work(fe); |
| |
| if (fe->work_q) |
| destroy_workqueue(fe->work_q); |
| |
| aml_fe_set_sys(fe->fe, SYS_UNDEFINED); |
| dvb_unregister_frontend(fe->fe); |
| dvb_frontend_detach(fe->fe); |
| |
| fe->tuner = NULL; |
| fe->atv_demod = NULL; |
| fe->dtv_demod = NULL; |
| fe->init = 0; |
| } |
| |
| return 0; |
| } |
| |
| void aml_fe_set_pdata(struct aml_fe_dev *dev, void *pdata) |
| { |
| dev->priv_data = pdata; |
| } |
| EXPORT_SYMBOL(aml_fe_set_pdata); |
| |
| void *aml_fe_get_pdata(struct aml_fe_dev *dev) |
| { |
| return dev->priv_data; |
| } |
| EXPORT_SYMBOL(aml_fe_get_pdata); |
| |
| static void aml_fe_do_work(struct work_struct *work) |
| { |
| struct aml_fe *fe; |
| |
| fe = container_of(work, struct aml_fe, work); |
| |
| if (fe->do_work) |
| fe->do_work(fe); |
| |
| fe->work_running = 0; |
| } |
| |
| void aml_fe_schedule_work(struct aml_fe *fe, void(*func)(struct aml_fe *fe)) |
| { |
| if (fe->work_running) |
| cancel_work_sync(&fe->work); |
| |
| fe->work_running = 1; |
| fe->do_work = func; |
| |
| if (!fe->work_q) { |
| fe->work_q = create_singlethread_workqueue("amlfe"); |
| INIT_WORK(&fe->work, aml_fe_do_work); |
| } |
| |
| queue_work(fe->work_q, &fe->work); |
| } |
| EXPORT_SYMBOL(aml_fe_schedule_work); |
| |
| void aml_fe_cancel_work(struct aml_fe *fe) |
| { |
| if (fe->work_running) { |
| fe->work_running = 0; |
| cancel_work_sync(&fe->work); |
| } |
| |
| fe->do_work = NULL; |
| } |
| EXPORT_SYMBOL(aml_fe_cancel_work); |
| |
| |
| int aml_fe_work_cancelled(struct aml_fe *fe) |
| { |
| return fe->work_running ? 0 : 1; |
| } |
| EXPORT_SYMBOL(aml_fe_work_cancelled); |
| |
| int aml_fe_work_sleep(struct aml_fe *fe, unsigned long delay) |
| { |
| wait_event_interruptible_timeout(fe->wait_q, !fe->work_running, delay); |
| return aml_fe_work_cancelled(fe); |
| } |
| EXPORT_SYMBOL(aml_fe_work_sleep); |
| |
| static ssize_t tuner_name_show(struct class *cls, struct class_attribute *attr, |
| char *buf) |
| { |
| size_t len = 0; |
| struct aml_fe_drv *drv; |
| unsigned long flags; |
| |
| struct aml_fe_drv **list = aml_get_fe_drv_list(AM_DEV_TUNER); |
| |
| spin_lock_irqsave(&lock, flags); |
| for (drv = *list; drv; drv = drv->next) |
| len += sprintf(buf + len, "%s\n", drv->name); |
| spin_unlock_irqrestore(&lock, flags); |
| return len; |
| } |
| |
| static ssize_t atv_demod_name_show(struct class *cls, |
| struct class_attribute *attr, char *buf) |
| { |
| size_t len = 0; |
| struct aml_fe_drv *drv; |
| unsigned long flags; |
| |
| struct aml_fe_drv **list = aml_get_fe_drv_list(AM_DEV_ATV_DEMOD); |
| |
| spin_lock_irqsave(&lock, flags); |
| for (drv = *list; drv; drv = drv->next) |
| len += sprintf(buf + len, "%s\n", drv->name); |
| spin_unlock_irqrestore(&lock, flags); |
| return len; |
| } |
| |
| static ssize_t dtv_demod_name_show(struct class *cls, |
| struct class_attribute *attr, char *buf) |
| { |
| size_t len = 0; |
| struct aml_fe_drv *drv; |
| unsigned long flags; |
| |
| struct aml_fe_drv **list = aml_get_fe_drv_list(AM_DEV_DTV_DEMOD); |
| |
| spin_lock_irqsave(&lock, flags); |
| for (drv = *list; drv; drv = drv->next) |
| len += sprintf(buf + len, "%s\n", drv->name); |
| spin_unlock_irqrestore(&lock, flags); |
| return len; |
| } |
| |
| static ssize_t setting_show(struct class *cls, struct class_attribute *attr, |
| char *buf) |
| { |
| int r, total = 0; |
| int i; |
| struct aml_fe_man *fm = &fe_man; |
| |
| r = sprintf(buf, "tuner:\n"); |
| buf += r; |
| total += r; |
| for (i = 0; i < FE_DEV_COUNT; i++) { |
| struct aml_fe_dev *dev = &fm->tuner[i]; |
| |
| if (dev->drv) { |
| r = sprintf(buf, "\t%d: %s\n", i, dev->drv->name); |
| buf += r; |
| total += r; |
| } |
| } |
| |
| r = sprintf(buf, "atv_demod:\n"); |
| buf += r; |
| total += r; |
| for (i = 0; i < FE_DEV_COUNT; i++) { |
| struct aml_fe_dev *dev = &fm->atv_demod[i]; |
| |
| if (dev->drv) { |
| r = sprintf(buf, "\t%d: %s\n", i, dev->drv->name); |
| buf += r; |
| total += r; |
| } |
| } |
| |
| r = sprintf(buf, "dtv_demod:\n"); |
| buf += r; |
| total += r; |
| for (i = 0; i < FE_DEV_COUNT; i++) { |
| struct aml_fe_dev *dev = &fm->dtv_demod[i]; |
| |
| if (dev->drv) { |
| r = sprintf(buf, "\t%d: %s\n", i, dev->drv->name); |
| buf += r; |
| total += r; |
| } |
| } |
| |
| r = sprintf(buf, "frontend:\n"); |
| buf += r; |
| total += r; |
| for (i = 0; i < FE_DEV_COUNT; i++) { |
| struct aml_fe *fe = &fm->fe[i]; |
| |
| r = sprintf(buf, |
| "\t%d: %s device: %d ts: %d tuner: %s atv_demod: %s dtv_demod: %s\n", |
| i, fe->init ? "enabled" : "disabled", fe->dev_id, |
| fe->ts, fe->tuner ? fe->tuner->drv->name : "none", |
| fe->atv_demod ? fe->atv_demod->drv->name : "none", |
| fe->dtv_demod ? fe->dtv_demod->drv->name : "none"); |
| buf += r; |
| total += r; |
| } |
| |
| return total; |
| } |
| |
| static void reset_drv(int id, enum aml_fe_dev_type_t type, const char *name) |
| { |
| struct aml_fe_man *fm = &fe_man; |
| struct aml_fe_drv **list; |
| struct aml_fe_drv **pdrv; |
| struct aml_fe_drv *drv; |
| struct aml_fe_drv *old; |
| |
| if ((id < 0) || (id >= FE_DEV_COUNT)) |
| return; |
| |
| if (fm->fe[id].init) { |
| pr_error("cannot reset driver when the device is inused\n"); |
| return; |
| } |
| |
| list = aml_get_fe_drv_list(type); |
| for (drv = *list; drv; drv = drv->next) |
| if (!strcmp(drv->name, name)) |
| break; |
| |
| switch (type) { |
| case AM_DEV_TUNER: |
| pdrv = &fm->tuner[id].drv; |
| break; |
| case AM_DEV_ATV_DEMOD: |
| pdrv = &fm->atv_demod[id].drv; |
| break; |
| case AM_DEV_DTV_DEMOD: |
| pdrv = &fm->dtv_demod[id].drv; |
| break; |
| default: |
| return; |
| } |
| |
| old = *pdrv; |
| if (old == drv) |
| return; |
| |
| if (old) { |
| old->ref--; |
| if (old->owner) |
| module_put(old->owner); |
| } |
| |
| if (drv) { |
| drv->ref++; |
| if (drv->owner) |
| try_module_get(drv->owner); |
| } |
| |
| *pdrv = drv; |
| } |
| |
| static ssize_t setting_store(struct class *class, struct class_attribute *attr, |
| const char *buf, size_t size) |
| { |
| struct aml_fe_man *fm = &fe_man; |
| int id, val; |
| char dev_name[32]; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&lock, flags); |
| |
| if (sscanf(buf, "tuner %i driver %s", &id, dev_name) == 2) { |
| reset_drv(id, AM_DEV_TUNER, dev_name); |
| } else if (sscanf(buf, "atv_demod %i driver %s", &id, dev_name) == 2) { |
| reset_drv(id, AM_DEV_ATV_DEMOD, dev_name); |
| } else if (sscanf(buf, "dtv_demod %i driver %s", &id, dev_name) == 2) { |
| reset_drv(id, AM_DEV_DTV_DEMOD, dev_name); |
| } else if (sscanf(buf, "frontend %i device %i", &id, &val) == 2) { |
| if ((id >= 0) && (id < FE_DEV_COUNT)) |
| fm->fe[id].dev_id = val; |
| } else if (sscanf(buf, "frontend %i ts %i", &id, &val) == 2) { |
| if ((id >= 0) && (id < FE_DEV_COUNT)) |
| fm->fe[id].ts = val; |
| } else if (sscanf(buf, "frontend %i tuner %i", &id, &val) == 2) { |
| if ((id >= 0) && (id < FE_DEV_COUNT) && (val >= 0) |
| && (val < FE_DEV_COUNT) && fm->tuner[val].drv) |
| fm->fe[id].tuner = &fm->tuner[val]; |
| } else if (sscanf(buf, "frontend %i atv_demod %i", &id, &val) == 2) { |
| if ((id >= 0) && (id < FE_DEV_COUNT) && (val >= 0) |
| && (val < FE_DEV_COUNT) && fm->atv_demod[val].drv) |
| fm->fe[id].atv_demod = &fm->atv_demod[val]; |
| } else if (sscanf(buf, "frontend %i dtv_demod %i", &id, &val) == 2) { |
| if ((id >= 0) && (id < FE_DEV_COUNT) && (val >= 0) |
| && (val < FE_DEV_COUNT) && fm->dtv_demod[val].drv) |
| fm->fe[id].dtv_demod = &fm->dtv_demod[val]; |
| } |
| |
| spin_unlock_irqrestore(&lock, flags); |
| |
| if (sscanf(buf, "enable %i", &id) == 1) { |
| if ((id >= 0) && (id < FE_DEV_COUNT)) |
| aml_fe_man_run(&fm->fe[id]); |
| } else if (sscanf(buf, "disable %i", &id) == 1) { |
| if ((id >= 0) && (id < FE_DEV_COUNT)) |
| aml_fe_man_release(&fm->fe[id]); |
| } else if (strstr(buf, "autoload")) { |
| for (id = 0; id < FE_DEV_COUNT; id++) { |
| aml_fe_dev_init(fm, AM_DEV_TUNER, |
| &fm->tuner[id], id); |
| aml_fe_dev_init(fm, AM_DEV_ATV_DEMOD, |
| &fm->atv_demod[id], id); |
| aml_fe_dev_init(fm, AM_DEV_DTV_DEMOD, |
| &fm->dtv_demod[id], id); |
| } |
| |
| for (id = 0; id < FE_DEV_COUNT; id++) |
| aml_fe_man_init(fm, &fm->fe[id], id); |
| } else if (strstr(buf, "disableall")) { |
| for (id = 0; id < FE_DEV_COUNT; id++) |
| aml_fe_man_release(&fm->fe[id]); |
| |
| for (id = 0; id < FE_DEV_COUNT; id++) { |
| aml_fe_dev_release(&fm->dtv_demod[id]); |
| aml_fe_dev_release(&fm->atv_demod[id]); |
| aml_fe_dev_release(&fm->tuner[id]); |
| } |
| } |
| |
| return size; |
| } |
| |
| static ssize_t aml_fe_show_suspended_flag(struct class *class, |
| struct class_attribute *attr, |
| char *buf) |
| { |
| ssize_t ret = 0; |
| |
| ret = sprintf(buf, "%ld\n", aml_fe_suspended); |
| |
| return ret; |
| } |
| |
| static ssize_t aml_fe_store_suspended_flag(struct class *class, |
| struct class_attribute *attr, |
| const char *buf, size_t size) |
| { |
| /*aml_fe_suspended = simple_strtol(buf, 0, 0); */ |
| int ret = kstrtol(buf, 0, &aml_fe_suspended); |
| |
| if (ret) |
| return ret; |
| return size; |
| } |
| |
| static struct class_attribute aml_fe_cls_attrs[] = { |
| __ATTR(tuner_name, |
| 0644, |
| tuner_name_show, NULL), |
| __ATTR(atv_demod_name, |
| 0644, |
| atv_demod_name_show, NULL), |
| __ATTR(dtv_demod_name, |
| 0644, |
| dtv_demod_name_show, NULL), |
| __ATTR(setting, |
| 0644, |
| setting_show, setting_store), |
| __ATTR(aml_fe_suspended_flag, |
| 0644, |
| aml_fe_show_suspended_flag, |
| aml_fe_store_suspended_flag), |
| __ATTR_NULL |
| }; |
| |
| static struct class aml_fe_class = { |
| .name = "amlfe", |
| .class_attrs = aml_fe_cls_attrs, |
| }; |
| |
| static int aml_fe_probe(struct platform_device *pdev) |
| { |
| struct aml_dvb *dvb = aml_get_dvb_device(); |
| int i; |
| |
| fe_man.dvb = dvb; |
| fe_man.pdev = pdev; |
| |
| platform_set_drvdata(pdev, &fe_man); |
| |
| for (i = 0; i < FE_DEV_COUNT; i++) { |
| if (aml_fe_dev_init(&fe_man, |
| AM_DEV_TUNER, |
| &fe_man.tuner[i], i) < 0) |
| goto probe_end; |
| |
| if (aml_fe_dev_init(&fe_man, |
| AM_DEV_ATV_DEMOD, |
| &fe_man.atv_demod[i], i) < 0) |
| goto probe_end; |
| |
| if (aml_fe_dev_init(&fe_man, |
| AM_DEV_DTV_DEMOD, |
| &fe_man.dtv_demod[i], i) < 0) |
| goto probe_end; |
| } |
| |
| for (i = 0; i < FE_DEV_COUNT; i++) { |
| if (aml_fe_man_init(&fe_man, &fe_man.fe[i], i) < 0) |
| goto probe_end; |
| } |
| |
| probe_end: |
| |
| fe_man.pinctrl = devm_pinctrl_get_select_default(&pdev->dev); |
| |
| if (class_register(&aml_fe_class) < 0) |
| pr_error("[aml_fe..] register class error\n"); |
| |
| pr_dbg("[aml_fe..] probe ok.\n"); |
| |
| return 0; |
| } |
| |
| static int aml_fe_remove(struct platform_device *pdev) |
| { |
| struct aml_fe_man *fe_man = platform_get_drvdata(pdev); |
| int i; |
| |
| if (fe_man) { |
| platform_set_drvdata(pdev, NULL); |
| |
| for (i = 0; i < FE_DEV_COUNT; i++) |
| aml_fe_man_release(&fe_man->fe[i]); |
| for (i = 0; i < FE_DEV_COUNT; i++) { |
| aml_fe_dev_release(&fe_man->dtv_demod[i]); |
| aml_fe_dev_release(&fe_man->atv_demod[i]); |
| aml_fe_dev_release(&fe_man->tuner[i]); |
| } |
| |
| if (fe_man->pinctrl) |
| devm_pinctrl_put(fe_man->pinctrl); |
| } |
| |
| class_unregister(&aml_fe_class); |
| |
| return 0; |
| } |
| |
| static int aml_fe_suspend(struct platform_device *dev, pm_message_t state) |
| { |
| int i; |
| |
| for (i = 0; i < FE_DEV_COUNT; i++) { |
| struct aml_fe *fe = &fe_man.fe[i]; |
| |
| if (fe->tuner && fe->tuner->drv->suspend) |
| fe->tuner->drv->suspend(fe->tuner); |
| |
| if (fe->atv_demod && fe->atv_demod->drv->suspend) |
| fe->atv_demod->drv->suspend(fe->atv_demod); |
| |
| if (fe->dtv_demod && fe->dtv_demod->drv->suspend) |
| fe->dtv_demod->drv->suspend(fe->dtv_demod); |
| } |
| |
| aml_fe_suspended = 1; |
| |
| return 0; |
| } |
| |
| static int aml_fe_resume(struct platform_device *dev) |
| { |
| int i; |
| |
| aml_fe_suspended = 0; |
| |
| for (i = 0; i < FE_DEV_COUNT; i++) { |
| struct aml_fe *fe = &fe_man.fe[i]; |
| |
| if (fe->tuner && fe->tuner->drv->resume) |
| fe->tuner->drv->resume(fe->tuner); |
| |
| if (fe->atv_demod && fe->atv_demod->drv->resume) |
| fe->atv_demod->drv->resume(fe->atv_demod); |
| |
| if (fe->dtv_demod && fe->dtv_demod->drv->resume) |
| fe->dtv_demod->drv->resume(fe->dtv_demod); |
| } |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_OF |
| static const struct of_device_id aml_fe_dt_match[] = { |
| { |
| .compatible = "amlogic, dvbfe", |
| }, |
| {}, |
| }; |
| #endif /*CONFIG_OF */ |
| |
| static struct platform_driver aml_fe_driver = { |
| .probe = aml_fe_probe, |
| .remove = aml_fe_remove, |
| .suspend = aml_fe_suspend, |
| .resume = aml_fe_resume, |
| .driver = { |
| .name = "amlogic-dvb-fe", |
| .owner = THIS_MODULE, |
| #ifdef CONFIG_OF |
| .of_match_table = aml_fe_dt_match, |
| #endif |
| } |
| }; |
| |
| static int __init aml_fe_init(void) |
| { |
| return platform_driver_register(&aml_fe_driver); |
| } |
| |
| static void __exit aml_fe_exit(void) |
| { |
| platform_driver_unregister(&aml_fe_driver); |
| } |
| |
| module_init(aml_fe_init); |
| module_exit(aml_fe_exit); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("amlogic frontend driver"); |
| MODULE_AUTHOR("L+#= +0=1"); |
| |