diff options
author | 2021-01-26 02:54:13 +0100 | |
---|---|---|
committer | 2021-02-05 23:37:59 +0100 | |
commit | b53f2fa136b1b20f03184f4a98fea75eb2705eac (patch) | |
tree | d83ce886c4d7fe584ad3e4ac7f06249ce123a81b /drivers/media/dvb-frontends/mxl692.c | |
parent | Merge tag 'v5.11-rc6' into patchwork (diff) | |
download | linux-b53f2fa136b1b20f03184f4a98fea75eb2705eac.tar.gz linux-b53f2fa136b1b20f03184f4a98fea75eb2705eac.tar.bz2 linux-b53f2fa136b1b20f03184f4a98fea75eb2705eac.zip |
media: mxl692: MaxLinear 692 ATSC demod/tuner driver
MaxLinear 692 is a combo demod/tuner which has ATSC and QAM
capabilities. Only ATSC is currently advertised via DVB
properties. QAM still has issues.
Confirmed working on both big and little endian. Big
endian verified on PowerPC Mac mini.
Signed-off-by: Brad Love <brad@nextdimension.cc>
Signed-off-by: Sean Young <sean@mess.org>
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
Diffstat (limited to 'drivers/media/dvb-frontends/mxl692.c')
-rw-r--r-- | drivers/media/dvb-frontends/mxl692.c | 1378 |
1 files changed, 1378 insertions, 0 deletions
diff --git a/drivers/media/dvb-frontends/mxl692.c b/drivers/media/dvb-frontends/mxl692.c new file mode 100644 index 000000000000..86af831c0176 --- /dev/null +++ b/drivers/media/dvb-frontends/mxl692.c @@ -0,0 +1,1378 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the MaxLinear MxL69x family of combo tuners/demods + * + * Copyright (C) 2020 Brad Love <brad@nextdimension.cc> + * + * based on code: + * Copyright (c) 2016 MaxLinear, Inc. All rights reserved + * which was released under GPL V2 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/mutex.h> +#include <linux/i2c-mux.h> +#include <linux/string.h> +#include <linux/firmware.h> + +#include "mxl692.h" +#include "mxl692_defs.h" + +static const struct dvb_frontend_ops mxl692_ops; + +struct mxl692_dev { + struct dvb_frontend fe; + struct i2c_client *i2c_client; + struct mutex i2c_lock; /* i2c command mutex */ + enum MXL_EAGLE_DEMOD_TYPE_E demod_type; + enum MXL_EAGLE_POWER_MODE_E power_mode; + u32 current_frequency; + int device_type; + int seqnum; + int init_done; +}; + +static int mxl692_i2c_write(struct mxl692_dev *dev, u8 *buffer, u16 buf_len) +{ + int ret = 0; + struct i2c_msg msg = { + .addr = dev->i2c_client->addr, + .flags = 0, + .buf = buffer, + .len = buf_len + }; + + ret = i2c_transfer(dev->i2c_client->adapter, &msg, 1); + if (ret != 1) + dev_dbg(&dev->i2c_client->dev, "i2c write error!\n"); + + return ret; +} + +static int mxl692_i2c_read(struct mxl692_dev *dev, u8 *buffer, u16 buf_len) +{ + int ret = 0; + struct i2c_msg msg = { + .addr = dev->i2c_client->addr, + .flags = I2C_M_RD, + .buf = buffer, + .len = buf_len + }; + + ret = i2c_transfer(dev->i2c_client->adapter, &msg, 1); + if (ret != 1) + dev_dbg(&dev->i2c_client->dev, "i2c read error!\n"); + + return ret; +} + +static int convert_endian(u32 size, u8 *d) +{ + u32 i; + + for (i = 0; i < (size & ~3); i += 4) { + d[i + 0] ^= d[i + 3]; + d[i + 3] ^= d[i + 0]; + d[i + 0] ^= d[i + 3]; + + d[i + 1] ^= d[i + 2]; + d[i + 2] ^= d[i + 1]; + d[i + 1] ^= d[i + 2]; + } + + switch (size & 3) { + case 0: + case 1: + /* do nothing */ + break; + case 2: + d[i + 0] ^= d[i + 1]; + d[i + 1] ^= d[i + 0]; + d[i + 0] ^= d[i + 1]; + break; + + case 3: + d[i + 0] ^= d[i + 2]; + d[i + 2] ^= d[i + 0]; + d[i + 0] ^= d[i + 2]; + break; + } + return size; +} + +static int convert_endian_n(int n, u32 size, u8 *d) +{ + int i, count = 0; + + for (i = 0; i < n; i += size) + count += convert_endian(size, d + i); + return count; +} + +static void mxl692_tx_swap(enum MXL_EAGLE_OPCODE_E opcode, u8 *buffer) +{ +#ifdef __BIG_ENDIAN + return; +#endif + buffer += MXL_EAGLE_HOST_MSG_HEADER_SIZE; /* skip API header */ + + switch (opcode) { + case MXL_EAGLE_OPCODE_DEVICE_INTR_MASK_SET: + case MXL_EAGLE_OPCODE_TUNER_CHANNEL_TUNE_SET: + case MXL_EAGLE_OPCODE_SMA_TRANSMIT_SET: + buffer += convert_endian(sizeof(u32), buffer); + break; + case MXL_EAGLE_OPCODE_QAM_PARAMS_SET: + buffer += 5; + buffer += convert_endian(2 * sizeof(u32), buffer); + break; + default: + /* no swapping - all get opcodes */ + /* ATSC/OOB no swapping */ + break; + } +} + +static void mxl692_rx_swap(enum MXL_EAGLE_OPCODE_E opcode, u8 *buffer) +{ +#ifdef __BIG_ENDIAN + return; +#endif + buffer += MXL_EAGLE_HOST_MSG_HEADER_SIZE; /* skip API header */ + + switch (opcode) { + case MXL_EAGLE_OPCODE_TUNER_AGC_STATUS_GET: + buffer++; + buffer += convert_endian(2 * sizeof(u16), buffer); + break; + case MXL_EAGLE_OPCODE_ATSC_STATUS_GET: + buffer += convert_endian_n(2, sizeof(u16), buffer); + buffer += convert_endian(sizeof(u32), buffer); + break; + case MXL_EAGLE_OPCODE_ATSC_ERROR_COUNTERS_GET: + buffer += convert_endian(3 * sizeof(u32), buffer); + break; + case MXL_EAGLE_OPCODE_ATSC_EQUALIZER_FILTER_FFE_TAPS_GET: + buffer += convert_endian_n(24, sizeof(u16), buffer); + break; + case MXL_EAGLE_OPCODE_QAM_STATUS_GET: + buffer += 8; + buffer += convert_endian_n(2, sizeof(u16), buffer); + buffer += convert_endian(sizeof(u32), buffer); + break; + case MXL_EAGLE_OPCODE_QAM_ERROR_COUNTERS_GET: + buffer += convert_endian(7 * sizeof(u32), buffer); + break; + case MXL_EAGLE_OPCODE_QAM_CONSTELLATION_VALUE_GET: + case MXL_EAGLE_OPCODE_QAM_EQUALIZER_FILTER_DFE_START_GET: + case MXL_EAGLE_OPCODE_QAM_EQUALIZER_FILTER_DFE_MIDDLE_GET: + case MXL_EAGLE_OPCODE_QAM_EQUALIZER_FILTER_DFE_END_GET: + case MXL_EAGLE_OPCODE_QAM_EQUALIZER_FILTER_SPUR_START_GET: + buffer += convert_endian_n(24, sizeof(u16), buffer); + break; + case MXL_EAGLE_OPCODE_QAM_EQUALIZER_FILTER_SPUR_END_GET: + buffer += convert_endian_n(8, sizeof(u16), buffer); + break; + case MXL_EAGLE_OPCODE_QAM_EQUALIZER_FILTER_FFE_GET: + buffer += convert_endian_n(17, sizeof(u16), buffer); + break; + case MXL_EAGLE_OPCODE_OOB_ERROR_COUNTERS_GET: + buffer += convert_endian(3 * sizeof(u32), buffer); + break; + case MXL_EAGLE_OPCODE_OOB_STATUS_GET: + buffer += convert_endian_n(2, sizeof(u16), buffer); + buffer += convert_endian(sizeof(u32), buffer); + break; + case MXL_EAGLE_OPCODE_SMA_RECEIVE_GET: + buffer += convert_endian(sizeof(u32), buffer); + break; + default: + /* no swapping - all set opcodes */ + break; + } +} + +static u32 mxl692_checksum(u8 *buffer, u32 size) +{ + u32 ix, div_size; + u32 cur_cksum = 0; + __be32 *buf; + + div_size = DIV_ROUND_UP(size, 4); + + buf = (__be32 *)buffer; + for (ix = 0; ix < div_size; ix++) + cur_cksum += be32_to_cpu(buf[ix]); + + cur_cksum ^= 0xDEADBEEF; + + return cur_cksum; +} + +static int mxl692_validate_fw_header(struct mxl692_dev *dev, + const u8 *buffer, u32 buf_len) +{ + int status = 0; + u32 ix, temp; + __be32 *local_buf = NULL; + u8 temp_cksum = 0; + const u8 fw_hdr[] = { 0x4D, 0x31, 0x10, 0x02, 0x40, 0x00, 0x00, 0x80 }; + + if (memcmp(buffer, fw_hdr, 8) != 0) { + status = -EINVAL; + goto err_finish; + } + + local_buf = (__be32 *)(buffer + 8); + temp = be32_to_cpu(*local_buf); + + if ((buf_len - 16) != temp >> 8) { + status = -EINVAL; + goto err_finish; + } + + for (ix = 16; ix < buf_len; ix++) + temp_cksum += buffer[ix]; + + if (temp_cksum != buffer[11]) + status = -EINVAL; + +err_finish: + if (status) + dev_dbg(&dev->i2c_client->dev, "failed\n"); + return status; +} + +static int mxl692_write_fw_block(struct mxl692_dev *dev, const u8 *buffer, + u32 buf_len, u32 *index) +{ + int status = 0; + u32 ix = 0, total_len = 0, addr = 0, chunk_len = 0, prevchunk_len = 0; + u8 local_buf[MXL_EAGLE_MAX_I2C_PACKET_SIZE] = {}, *plocal_buf = NULL; + int payload_max = MXL_EAGLE_MAX_I2C_PACKET_SIZE - MXL_EAGLE_I2C_MHEADER_SIZE; + + ix = *index; + + if (buffer[ix] == 0x53) { + total_len = buffer[ix + 1] << 16 | buffer[ix + 2] << 8 | buffer[ix + 3]; + total_len = (total_len + 3) & ~3; + addr = buffer[ix + 4] << 24 | buffer[ix + 5] << 16 | + buffer[ix + 6] << 8 | buffer[ix + 7]; + ix += MXL_EAGLE_FW_SEGMENT_HEADER_SIZE; + + while ((total_len > 0) && (status == 0)) { + plocal_buf = local_buf; + chunk_len = (total_len < payload_max) ? total_len : payload_max; + + *plocal_buf++ = 0xFC; + *plocal_buf++ = chunk_len + sizeof(u32); + + *(u32 *)plocal_buf = addr + prevchunk_len; +#ifdef __BIG_ENDIAN + convert_endian(sizeof(u32), plocal_buf); +#endif + plocal_buf += sizeof(u32); + + memcpy(plocal_buf, &buffer[ix], chunk_len); + convert_endian(chunk_len, plocal_buf); + if (mxl692_i2c_write(dev, local_buf, + (chunk_len + MXL_EAGLE_I2C_MHEADER_SIZE)) < 0) { + status = -EREMOTEIO; + break; + } + + prevchunk_len += chunk_len; + total_len -= chunk_len; + ix += chunk_len; + } + *index = ix; + } else { + status = -EINVAL; + } + + if (status) + dev_dbg(&dev->i2c_client->dev, "err %d\n", status); + + return status; +} + +static int mxl692_memwrite(struct mxl692_dev *dev, u32 addr, + u8 *buffer, u32 size) +{ + int status = 0, total_len = 0; + u8 local_buf[MXL_EAGLE_MAX_I2C_PACKET_SIZE] = {}, *plocal_buf = NULL; + + total_len = size; + total_len = (total_len + 3) & ~3; /* 4 byte alignment */ + + if (total_len > (MXL_EAGLE_MAX_I2C_PACKET_SIZE - MXL_EAGLE_I2C_MHEADER_SIZE)) + dev_dbg(&dev->i2c_client->dev, "hrmph?\n"); + + plocal_buf = local_buf; + + *plocal_buf++ = 0xFC; + *plocal_buf++ = total_len + sizeof(u32); + + *(u32 *)plocal_buf = addr; + plocal_buf += sizeof(u32); + + memcpy(plocal_buf, buffer, total_len); +#ifdef __BIG_ENDIAN + convert_endian(sizeof(u32) + total_len, local_buf + 2); +#endif + if (mxl692_i2c_write(dev, local_buf, + (total_len + MXL_EAGLE_I2C_MHEADER_SIZE)) < 0) { + status = -EREMOTEIO; + goto err_finish; + } + + return status; +err_finish: + dev_dbg(&dev->i2c_client->dev, "err %d\n", status); + return status; +} + +static int mxl692_memread(struct mxl692_dev *dev, u32 addr, + u8 *buffer, u32 size) +{ + int status = 0; + u8 local_buf[MXL_EAGLE_I2C_MHEADER_SIZE] = {}, *plocal_buf = NULL; + + plocal_buf = local_buf; + + *plocal_buf++ = 0xFB; + *plocal_buf++ = sizeof(u32); + *(u32 *)plocal_buf = addr; +#ifdef __BIG_ENDIAN + convert_endian(sizeof(u32), plocal_buf); +#endif + mutex_lock(&dev->i2c_lock); + + if (mxl692_i2c_write(dev, local_buf, MXL_EAGLE_I2C_MHEADER_SIZE) > 0) { + size = (size + 3) & ~3; /* 4 byte alignment */ + status = mxl692_i2c_read(dev, buffer, (u16)size) < 0 ? -EREMOTEIO : 0; +#ifdef __BIG_ENDIAN + if (status == 0) + convert_endian(size, buffer); +#endif + } else { + status = -EREMOTEIO; + } + + mutex_unlock(&dev->i2c_lock); + + if (status) + dev_dbg(&dev->i2c_client->dev, "err %d\n", status); + + return status; +} + +static const char *mxl692_opcode_string(u8 opcode) +{ + if (opcode >= 0 && opcode <= MXL_EAGLE_OPCODE_INTERNAL) + return MXL_EAGLE_OPCODE_STRING[opcode]; + + return "invalid opcode"; +} + +static int mxl692_opwrite(struct mxl692_dev *dev, u8 *buffer, + u32 size) +{ + int status = 0, total_len = 0; + u8 local_buf[MXL_EAGLE_MAX_I2C_PACKET_SIZE] = {}, *plocal_buf = NULL; + struct MXL_EAGLE_HOST_MSG_HEADER_T *tx_hdr = (struct MXL_EAGLE_HOST_MSG_HEADER_T *)buffer; + + total_len = size; + total_len = (total_len + 3) & ~3; /* 4 byte alignment */ + + if (total_len > (MXL_EAGLE_MAX_I2C_PACKET_SIZE - MXL_EAGLE_I2C_PHEADER_SIZE)) + dev_dbg(&dev->i2c_client->dev, "hrmph?\n"); + + plocal_buf = local_buf; + + *plocal_buf++ = 0xFE; + *plocal_buf++ = (u8)total_len; + + memcpy(plocal_buf, buffer, total_len); + convert_endian(total_len, plocal_buf); + + if (mxl692_i2c_write(dev, local_buf, + (total_len + MXL_EAGLE_I2C_PHEADER_SIZE)) < 0) { + status = -EREMOTEIO; + goto err_finish; + } +err_finish: + if (status) + dev_dbg(&dev->i2c_client->dev, "opcode %s err %d\n", + mxl692_opcode_string(tx_hdr->opcode), status); + return status; +} + +static int mxl692_opread(struct mxl692_dev *dev, u8 *buffer, + u32 size) +{ + int status = 0; + u32 ix = 0; + u8 local_buf[MXL_EAGLE_I2C_PHEADER_SIZE] = {}; + + local_buf[0] = 0xFD; + local_buf[1] = 0; + + if (mxl692_i2c_write(dev, local_buf, MXL_EAGLE_I2C_PHEADER_SIZE) > 0) { + size = (size + 3) & ~3; /* 4 byte alignment */ + + /* Read in 4 byte chunks */ + for (ix = 0; ix < size; ix += 4) { + if (mxl692_i2c_read(dev, buffer + ix, 4) < 0) { + dev_dbg(&dev->i2c_client->dev, "ix=%d size=%d\n", ix, size); + status = -EREMOTEIO; + goto err_finish; + } + } + convert_endian(size, buffer); + } else { + status = -EREMOTEIO; + } +err_finish: + if (status) + dev_dbg(&dev->i2c_client->dev, "err %d\n", status); + return status; +} + +static int mxl692_i2c_writeread(struct mxl692_dev *dev, + u8 opcode, + u8 *tx_payload, + u8 tx_payload_size, + u8 *rx_payload, + u8 rx_payload_expected) +{ + int status = 0, timeout = 40; + u8 tx_buf[MXL_EAGLE_MAX_I2C_PACKET_SIZE] = {}; + u8 rx_buf[MXL_EAGLE_MAX_I2C_PACKET_SIZE] = {}; + u32 resp_checksum = 0, resp_checksum_tmp = 0; + struct MXL_EAGLE_HOST_MSG_HEADER_T *tx_header; + struct MXL_EAGLE_HOST_MSG_HEADER_T *rx_header; + + mutex_lock(&dev->i2c_lock); + + if ((tx_payload_size + MXL_EAGLE_HOST_MSG_HEADER_SIZE) > + (MXL_EAGLE_MAX_I2C_PACKET_SIZE - MXL_EAGLE_I2C_PHEADER_SIZE)) { + status = -EINVAL; + goto err_finish; + } + + tx_header = (struct MXL_EAGLE_HOST_MSG_HEADER_T *)tx_buf; + tx_header->opcode = opcode; + tx_header->seqnum = dev->seqnum++; + tx_header->payload_size = tx_payload_size; + tx_header->checksum = 0; + + if (dev->seqnum == 0) + dev->seqnum = 1; + + if (tx_payload && tx_payload_size > 0) + memcpy(&tx_buf[MXL_EAGLE_HOST_MSG_HEADER_SIZE], tx_payload, tx_payload_size); + + mxl692_tx_swap(opcode, tx_buf); + + tx_header->checksum = 0; + tx_header->checksum = mxl692_checksum(tx_buf, + MXL_EAGLE_HOST_MSG_HEADER_SIZE + tx_payload_size); +#ifdef __LITTLE_ENDIAN + convert_endian(4, (u8 *)&tx_header->checksum); /* cksum is big endian */ +#endif + /* send Tx message */ + status = mxl692_opwrite(dev, tx_buf, + tx_payload_size + MXL_EAGLE_HOST_MSG_HEADER_SIZE); + if (status) { + status = -EREMOTEIO; + goto err_finish; + } + + /* receive Rx message (polling) */ + rx_header = (struct MXL_EAGLE_HOST_MSG_HEADER_T *)rx_buf; + + do { + status = mxl692_opread(dev, rx_buf, + rx_payload_expected + MXL_EAGLE_HOST_MSG_HEADER_SIZE); + usleep_range(1000, 2000); + timeout--; + } while ((timeout > 0) && (status == 0) && + (rx_header->seqnum == 0) && + (rx_header->checksum == 0)); + + if (timeout == 0 || status) { + dev_dbg(&dev->i2c_client->dev, "timeout=%d status=%d\n", + timeout, status); + status = -ETIMEDOUT; + goto err_finish; + } + + if (rx_header->status) { + dev_dbg(&dev->i2c_client->dev, "rx header status code: %d\n", rx_header->status); + status = -EREMOTEIO; + goto err_finish; + } + + if (rx_header->seqnum != tx_header->seqnum || + rx_header->opcode != tx_header->opcode || + rx_header->payload_size != rx_payload_expected) { + dev_dbg(&dev->i2c_client->dev, "Something failed seq=%s opcode=%s pSize=%s\n", + rx_header->seqnum != tx_header->seqnum ? "X" : "0", + rx_header->opcode != tx_header->opcode ? "X" : "0", + rx_header->payload_size != rx_payload_expected ? "X" : "0"); + if (rx_header->payload_size != rx_payload_expected) + dev_dbg(&dev->i2c_client->dev, + "rx_header->payloadSize=%d rx_payload_expected=%d\n", + rx_header->payload_size, rx_payload_expected); + status = -EREMOTEIO; + goto err_finish; + } + + resp_checksum = rx_header->checksum; + rx_header->checksum = 0; + + resp_checksum_tmp = mxl692_checksum(rx_buf, + MXL_EAGLE_HOST_MSG_HEADER_SIZE + rx_header->payload_size); +#ifdef __LITTLE_ENDIAN + convert_endian(4, (u8 *)&resp_checksum_tmp); /* cksum is big endian */ +#endif + if (resp_checksum != resp_checksum_tmp) { + dev_dbg(&dev->i2c_client->dev, "rx checksum failure\n"); + status = -EREMOTEIO; + goto err_finish; + } + + mxl692_rx_swap(rx_header->opcode, rx_buf); + + if (rx_header->payload_size > 0) { + if (!rx_payload) { + dev_dbg(&dev->i2c_client->dev, "no rx payload?!?\n"); + status = -EREMOTEIO; + goto err_finish; + } + memcpy(rx_payload, rx_buf + MXL_EAGLE_HOST_MSG_HEADER_SIZE, + rx_header->payload_size); + } +err_finish: + if (status) + dev_dbg(&dev->i2c_client->dev, "err %d\n", status); + + mutex_unlock(&dev->i2c_lock); + return status; +} + +static int mxl692_fwdownload(struct mxl692_dev *dev, + const u8 *firmware_buf, u32 buf_len) +{ + int status = 0; + u32 ix, reg_val = 0x1; + u8 rx_buf[MXL_EAGLE_MAX_I2C_PACKET_SIZE] = {}; + struct MXL_EAGLE_DEV_STATUS_T *dev_status; + + if (buf_len < MXL_EAGLE_FW_HEADER_SIZE || + buf_len > MXL_EAGLE_FW_MAX_SIZE_IN_KB * 1000) + return -EINVAL; + + mutex_lock(&dev->i2c_lock); + + dev_dbg(&dev->i2c_client->dev, "\n"); + + status = mxl692_validate_fw_header(dev, firmware_buf, buf_len); + if (status) + goto err_finish; + + ix = 16; + status = mxl692_write_fw_block(dev, firmware_buf, buf_len, &ix); /* DRAM */ + if (status) + goto err_finish; + + status = mxl692_write_fw_block(dev, firmware_buf, buf_len, &ix); /* IRAM */ + if (status) + goto err_finish; + + /* release CPU from reset */ + status = mxl692_memwrite(dev, 0x70000018, (u8 *)®_val, sizeof(u32)); + if (status) + goto err_finish; + + mutex_unlock(&dev->i2c_lock); + + if (status == 0) { + /* verify FW is alive */ + usleep_range(MXL_EAGLE_FW_LOAD_TIME * 1000, (MXL_EAGLE_FW_LOAD_TIME + 5) * 1000); + dev_status = (struct MXL_EAGLE_DEV_STATUS_T *)&rx_buf; + status = mxl692_i2c_writeread(dev, + MXL_EAGLE_OPCODE_DEVICE_STATUS_GET, + NULL, + 0, + (u8 *)dev_status, + sizeof(struct MXL_EAGLE_DEV_STATUS_T)); + } + + return status; +err_finish: + mutex_unlock(&dev->i2c_lock); + if (status) + dev_dbg(&dev->i2c_client->dev, "err %d\n", status); + return status; +} + +static int mxl692_get_versions(struct mxl692_dev *dev) +{ + int status = 0; + struct MXL_EAGLE_DEV_VER_T dev_ver = {}; + static const char * const chip_id[] = {"N/A", "691", "248", "692"}; + + status = mxl692_i2c_writeread(dev, MXL_EAGLE_OPCODE_DEVICE_VERSION_GET, + NULL, + 0, + (u8 *)&dev_ver, + sizeof(struct MXL_EAGLE_DEV_VER_T)); + if (status) + return status; + + dev_info(&dev->i2c_client->dev, "MxL692_DEMOD Chip ID: %s\n", + chip_id[dev_ver.chip_id]); + + dev_info(&dev->i2c_client->dev, + "MxL692_DEMOD FW Version: %d.%d.%d.%d_RC%d\n", + dev_ver.firmware_ver[0], + dev_ver.firmware_ver[1], + dev_ver.firmware_ver[2], + dev_ver.firmware_ver[3], + dev_ver.firmware_ver[4]); + + return status; +} + +static int mxl692_reset(struct mxl692_dev *dev) +{ + int status = 0; + u32 dev_type = MXL_EAGLE_DEVICE_MAX, reg_val = 0x2; + + dev_dbg(&dev->i2c_client->dev, "\n"); + + /* legacy i2c override */ + status = mxl692_memwrite(dev, 0x80000100, (u8 *)®_val, sizeof(u32)); + if (status) + goto err_finish; + + /* verify sku */ + status = mxl692_memread(dev, 0x70000188, (u8 *)&dev_type, sizeof(u32)); + if (status) + goto err_finish; + + if (dev_type != dev->device_type) + goto err_finish; + +err_finish: + if (status) + dev_dbg(&dev->i2c_client->dev, "err %d\n", status); + return status; +} + +static int mxl692_config_regulators(struct mxl692_dev *dev, + enum MXL_EAGLE_POWER_SUPPLY_SOURCE_E power_supply) +{ + int status = 0; + u32 reg_val; + + dev_dbg(&dev->i2c_client->dev, "\n"); + + /* configure main regulator according to the power supply source */ + status = mxl692_memread(dev, 0x90000000, (u8 *)®_val, sizeof(u32)); + if (status) + goto err_finish; + + reg_val &= 0x00FFFFFF; + reg_val |= (power_supply == MXL_EAGLE_POWER_SUPPLY_SOURCE_SINGLE) ? + 0x14000000 : 0x10000000; + + status = mxl692_memwrite(dev, 0x90000000, (u8 *)®_val, sizeof(u32)); + if (status) + goto err_finish; + + /* configure digital regulator to high current mode */ + status = mxl692_memread(dev, 0x90000018, (u8 *)®_val, sizeof(u32)); + if (status) + goto err_finish; + + reg_val |= 0x800; + + status = mxl692_memwrite(dev, 0x90000018, (u8 *)®_val, sizeof(u32)); + +err_finish: + if (status) + dev_dbg(&dev->i2c_client->dev, "err %d\n", status); + return status; +} + +static int mxl692_config_xtal(struct mxl692_dev *dev, + struct MXL_EAGLE_DEV_XTAL_T *dev_xtal) +{ + int status = 0; + u32 reg_val, reg_val1; + + dev_dbg(&dev->i2c_client->dev, "\n"); + + status = mxl692_memread(dev, 0x90000000, (u8 *)®_val, sizeof(u32)); + if (status) + goto err_finish; + + /* set XTAL capacitance */ + reg_val &= 0xFFFFFFE0; + reg_val |= dev_xtal->xtal_cap; + + /* set CLK OUT */ + reg_val = dev_xtal->clk_out_enable ? (reg_val | 0x0100) : (reg_val & 0xFFFFFEFF); + + status = mxl692_memwrite(dev, 0x90000000, (u8 *)®_val, sizeof(u32)); + if (status) + goto err_finish; + + /* set CLK OUT divider */ + reg_val = dev_xtal->clk_out_div_enable ? (reg_val | 0x0200) : (reg_val & 0xFFFFFDFF); + + status = mxl692_memwrite(dev, 0x90000000, (u8 *)®_val, sizeof(u32)); + if (status) + goto err_finish; + + /* set XTAL sharing */ + reg_val = dev_xtal->xtal_sharing_enable ? (reg_val | 0x010400) : (reg_val & 0xFFFEFBFF); + + status = mxl692_memwrite(dev, 0x90000000, (u8 *)®_val, sizeof(u32)); + if (status) + goto err_finish; + + /* enable/disable XTAL calibration, based on master/slave device */ + status = mxl692_memread(dev, 0x90000030, (u8 *)®_val1, sizeof(u32)); + if (status) + goto err_finish; + + if (dev_xtal->xtal_calibration_enable) { + /* enable XTAL calibration and set XTAL amplitude to a higher value */ + reg_val1 &= 0xFFFFFFFD; + reg_val1 |= 0x30; + + status = mxl692_memwrite(dev, 0x90000030, (u8 *)®_val1, sizeof(u32)); + if (status) + goto err_finish; + } else { + /* disable XTAL calibration */ + reg_val1 |= 0x2; + + status = mxl692_memwrite(dev, 0x90000030, (u8 *)®_val1, sizeof(u32)); + if (status) + goto err_finish; + + /* set XTAL bias value */ + status = mxl692_memread(dev, 0x9000002c, (u8 *)®_val, sizeof(u32)); + if (status) + goto err_finish; + + reg_val &= 0xC0FFFFFF; + reg_val |= 0xA000000; + + status = mxl692_memwrite(dev, 0x9000002c, (u8 *)®_val, sizeof(u32)); + if (status) + goto err_finish; + } + + /* start XTAL calibration */ + status = mxl692_memread(dev, 0x70000010, (u8 *)®_val, sizeof(u32)); + if (status) + goto err_finish; + + reg_val |= 0x8; + + status = mxl692_memwrite(dev, 0x70000010, (u8 *)®_val, sizeof(u32)); + if (status) + goto err_finish; + + status = mxl692_memread(dev, 0x70000018, (u8 *)®_val, sizeof(u32)); + if (status) + goto err_finish; + + reg_val |= 0x10; + + status = mxl692_memwrite(dev, 0x70000018, (u8 *)®_val, sizeof(u32)); + if (status) + goto err_finish; + + status = mxl692_memread(dev, 0x9001014c, (u8 *)®_val, sizeof(u32)); + if (status) + goto err_finish; + + reg_val &= 0xFFFFEFFF; + + status = mxl692_memwrite(dev, 0x9001014c, (u8 *)®_val, sizeof(u32)); + if (status) + goto err_finish; + + reg_val |= 0x1000; + + status = mxl692_memwrite(dev, 0x9001014c, (u8 *)®_val, sizeof(u32)); + if (status) + goto err_finish; + + usleep_range(45000, 55000); + +err_finish: + if (status) + dev_dbg(&dev->i2c_client->dev, "err %d\n", status); + return status; +} + +static int mxl692_powermode(struct mxl692_dev *dev, + enum MXL_EAGLE_POWER_MODE_E power_mode) +{ + int status = 0; + u8 mode = power_mode; + + dev_dbg(&dev->i2c_client->dev, "%s\n", + power_mode == MXL_EAGLE_POWER_MODE_SLEEP ? "sleep" : "active"); + + status = mxl692_i2c_writeread(dev, + MXL_EAGLE_OPCODE_DEVICE_POWERMODE_SET, + &mode, + sizeof(u8), + NULL, + 0); + if (status) { + dev_dbg(&dev->i2c_client->dev, "err %d\n", status); + return status; + } + + dev->power_mode = power_mode; + + return status; +} + +static int mxl692_init(struct dvb_frontend *fe) +{ + struct mxl692_dev *dev = fe->demodulator_priv; + struct i2c_client *client = dev->i2c_client; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int status = 0; + const struct firmware *firmware; + struct MXL_EAGLE_DEV_XTAL_T xtal_config = {}; + + dev_dbg(&dev->i2c_client->dev, "\n"); + + if (dev->init_done) + goto warm; + + dev->seqnum = 1; + + status = mxl692_reset(dev); + if (status) + goto err; + + usleep_range(50 * 1000, 60 * 1000); /* was 1000! */ + + status = mxl692_config_regulators(dev, MXL_EAGLE_POWER_SUPPLY_SOURCE_DUAL); + if (status) + goto err; + + xtal_config.xtal_cap = 26; + xtal_config.clk_out_div_enable = 0; + xtal_config.clk_out_enable = 0; + xtal_config.xtal_calibration_enable = 0; + xtal_config.xtal_sharing_enable = 1; + status = mxl692_config_xtal(dev, &xtal_config); + if (status) + goto err; + + status = request_firmware(&firmware, MXL692_FIRMWARE, &client->dev); + if (status) { + dev_dbg(&dev->i2c_client->dev, "firmware missing? %s\n", + MXL692_FIRMWARE); + goto err; + } + + status = mxl692_fwdownload(dev, firmware->data, firmware->size); + if (status) + goto err_release_firmware; + + release_firmware(firmware); + + status = mxl692_get_versions(dev); + if (status) + goto err; + + dev->power_mode = MXL_EAGLE_POWER_MODE_SLEEP; +warm: + /* Config Device Power Mode */ + if (dev->power_mode != MXL_EAGLE_POWER_MODE_ACTIVE) { + status = mxl692_powermode(dev, MXL_EAGLE_POWER_MODE_ACTIVE); + if (status) + goto err; + + usleep_range(50 * 1000, 60 * 1000); /* was 500! */ + } + + /* Init stats here to indicate which stats are supported */ + c->cnr.len = 1; + c->cnr.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->post_bit_error.len = 1; + c->post_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->post_bit_count.len = 1; + c->post_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->block_error.len = 1; + c->block_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + + dev->init_done = 1; + return 0; +err_release_firmware: + release_firmware(firmware); +err: + dev_dbg(&dev->i2c_client->dev, "err %d\n", status); + return status; +} + +static int mxl692_sleep(struct dvb_frontend *fe) +{ + struct mxl692_dev *dev = fe->demodulator_priv; + + if (dev->power_mode != MXL_EAGLE_POWER_MODE_SLEEP) + mxl692_powermode(dev, MXL_EAGLE_POWER_MODE_SLEEP); + + return 0; +} + +static int mxl692_set_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + struct mxl692_dev *dev = fe->demodulator_priv; + + int status = 0; + enum MXL_EAGLE_DEMOD_TYPE_E demod_type; + struct MXL_EAGLE_MPEGOUT_PARAMS_T mpeg_params = {}; + enum MXL_EAGLE_QAM_DEMOD_ANNEX_TYPE_E qam_annex = MXL_EAGLE_QAM_DEMOD_ANNEX_B; + struct MXL_EAGLE_QAM_DEMOD_PARAMS_T qam_params = {}; + struct MXL_EAGLE_TUNER_CHANNEL_PARAMS_T tuner_params = {}; + u8 op_param = 0; + + dev_dbg(&dev->i2c_client->dev, "\n"); + + switch (p->modulation) { + case VSB_8: + demod_type = MXL_EAGLE_DEMOD_TYPE_ATSC; + break; + case QAM_AUTO: + case QAM_64: + case QAM_128: + case QAM_256: + demod_type = MXL_EAGLE_DEMOD_TYPE_QAM; + break; + default: + return -EINVAL; + } + + if (dev->current_frequency == p->frequency && dev->demod_type == demod_type) { + dev_dbg(&dev->i2c_client->dev, "already set up\n"); + return 0; + } + + dev->current_frequency = -1; + dev->demod_type = -1; + + op_param = demod_type; + status = mxl692_i2c_writeread(dev, + MXL_EAGLE_OPCODE_DEVICE_DEMODULATOR_TYPE_SET, + &op_param, + sizeof(u8), + NULL, + 0); + if (status) { + dev_dbg(&dev->i2c_client->dev, + "DEVICE_DEMODULATOR_TYPE_SET...FAIL err 0x%x\n", status); + goto err; + } + + usleep_range(20 * 1000, 30 * 1000); /* was 500! */ + + mpeg_params.mpeg_parallel = 0; + mpeg_params.msb_first = MXL_EAGLE_DATA_SERIAL_MSB_1ST; + mpeg_params.mpeg_sync_pulse_width = MXL_EAGLE_DATA_SYNC_WIDTH_BIT; + mpeg_params.mpeg_valid_pol = MXL_EAGLE_CLOCK_POSITIVE; + mpeg_params.mpeg_sync_pol = MXL_EAGLE_CLOCK_POSITIVE; + mpeg_params.mpeg_clk_pol = MXL_EAGLE_CLOCK_NEGATIVE; + mpeg_params.mpeg3wire_mode_enable = 0; + mpeg_params.mpeg_clk_freq = MXL_EAGLE_MPEG_CLOCK_27MHZ; + + switch (demod_type) { + case MXL_EAGLE_DEMOD_TYPE_ATSC: + status = mxl692_i2c_writeread(dev, + MXL_EAGLE_OPCODE_DEVICE_MPEG_OUT_PARAMS_SET, + (u8 *)&mpeg_params, + sizeof(struct MXL_EAGLE_MPEGOUT_PARAMS_T), + NULL, + 0); + if (status) + goto err; + break; + case MXL_EAGLE_DEMOD_TYPE_QAM: + if (qam_annex == MXL_EAGLE_QAM_DEMOD_ANNEX_A) + mpeg_params.msb_first = MXL_EAGLE_DATA_SERIAL_LSB_1ST; + status = mxl692_i2c_writeread(dev, + MXL_EAGLE_OPCODE_DEVICE_MPEG_OUT_PARAMS_SET, + (u8 *)&mpeg_params, + sizeof(struct MXL_EAGLE_MPEGOUT_PARAMS_T), + NULL, + 0); + if (status) + goto err; + + qam_params.annex_type = qam_annex; + qam_params.qam_type = MXL_EAGLE_QAM_DEMOD_AUTO; + qam_params.iq_flip = MXL_EAGLE_DEMOD_IQ_AUTO; + if (p->modulation == QAM_64) + qam_params.symbol_rate_hz = 5057000; + else + qam_params.symbol_rate_hz = 5361000; + + qam_params.symbol_rate_256qam_hz = 5361000; + + status = mxl692_i2c_writeread(dev, + MXL_EAGLE_OPCODE_QAM_PARAMS_SET, + (u8 *)&qam_params, + sizeof(struct MXL_EAGLE_QAM_DEMOD_PARAMS_T), + NULL, 0); + if (status) + goto err; + + break; + default: + break; + } + + usleep_range(20 * 1000, 30 * 1000); /* was 500! */ + + tuner_params.freq_hz = p->frequency; + tuner_params.bandwidth = MXL_EAGLE_TUNER_BW_6MHZ; + tuner_params.tune_mode = MXL_EAGLE_TUNER_CHANNEL_TUNE_MODE_VIEW; + + dev_dbg(&dev->i2c_client->dev, " Tuning Freq: %d %s\n", tuner_params.freq_hz, + demod_type == MXL_EAGLE_DEMOD_TYPE_ATSC ? "ATSC" : "QAM"); + + status = mxl692_i2c_writeread(dev, + MXL_EAGLE_OPCODE_TUNER_CHANNEL_TUNE_SET, + (u8 *)&tuner_params, + sizeof(struct MXL_EAGLE_TUNER_CHANNEL_PARAMS_T), + NULL, + 0); + if (status) + goto err; + + usleep_range(20 * 1000, 30 * 1000); /* was 500! */ + + switch (demod_type) { + case MXL_EAGLE_DEMOD_TYPE_ATSC: + status = mxl692_i2c_writeread(dev, + MXL_EAGLE_OPCODE_ATSC_INIT_SET, + NULL, 0, NULL, 0); + if (status) + goto err; + break; + case MXL_EAGLE_DEMOD_TYPE_QAM: + status = mxl692_i2c_writeread(dev, + MXL_EAGLE_OPCODE_QAM_RESTART_SET, + NULL, 0, NULL, 0); + if (status) + goto err; + break; + default: + break; + } + + dev->demod_type = demod_type; + dev->current_frequency = p->frequency; + + return 0; +err: + dev_dbg(&dev->i2c_client->dev, "err %d\n", status); + return status; +} + +static int mxl692_get_frontend(struct dvb_frontend *fe, + struct dtv_frontend_properties *p) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + + p->modulation = c->modulation; + p->frequency = c->frequency; + + return 0; +} + +static int mxl692_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct mxl692_dev *dev = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + u8 rx_buf[MXL_EAGLE_MAX_I2C_PACKET_SIZE] = {}; + struct MXL_EAGLE_ATSC_DEMOD_STATUS_T *atsc_status; + struct MXL_EAGLE_QAM_DEMOD_STATUS_T *qam_status; + enum MXL_EAGLE_DEMOD_TYPE_E demod_type = dev->demod_type; + int mxl_status = 0; + + *snr = 0; + + dev_dbg(&dev->i2c_client->dev, "\n"); + + atsc_status = (struct MXL_EAGLE_ATSC_DEMOD_STATUS_T *)&rx_buf; + qam_status = (struct MXL_EAGLE_QAM_DEMOD_STATUS_T *)&rx_buf; + + switch (demod_type) { + case MXL_EAGLE_DEMOD_TYPE_ATSC: + mxl_status = mxl692_i2c_writeread(dev, + MXL_EAGLE_OPCODE_ATSC_STATUS_GET, + NULL, + 0, + rx_buf, + sizeof(struct MXL_EAGLE_ATSC_DEMOD_STATUS_T)); + if (!mxl_status) { + *snr = (u16)(atsc_status->snr_db_tenths / 10); + c->cnr.stat[0].scale = FE_SCALE_DECIBEL; + c->cnr.stat[0].svalue = *snr; + } + break; + case MXL_EAGLE_DEMOD_TYPE_QAM: + mxl_status = mxl692_i2c_writeread(dev, + MXL_EAGLE_OPCODE_QAM_STATUS_GET, + NULL, + 0, + rx_buf, + sizeof(struct MXL_EAGLE_QAM_DEMOD_STATUS_T)); + if (!mxl_status) + *snr = (u16)(qam_status->snr_db_tenths / 10); + break; + case MXL_EAGLE_DEMOD_TYPE_OOB: + default: + break; + } + + if (mxl_status) + dev_dbg(&dev->i2c_client->dev, "err %d\n", mxl_status); + return mxl_status; +} + +static int mxl692_read_ber_ucb(struct dvb_frontend *fe) +{ + struct mxl692_dev *dev = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + u8 rx_buf[MXL_EAGLE_MAX_I2C_PACKET_SIZE] = {}; + struct MXL_EAGLE_ATSC_DEMOD_ERROR_COUNTERS_T *atsc_errors; + enum MXL_EAGLE_DEMOD_TYPE_E demod_type = dev->demod_type; + int mxl_status = 0; + u32 utmp; + + dev_dbg(&dev->i2c_client->dev, "\n"); + + atsc_errors = (struct MXL_EAGLE_ATSC_DEMOD_ERROR_COUNTERS_T *)&rx_buf; + + switch (demod_type) { + case MXL_EAGLE_DEMOD_TYPE_ATSC: + mxl_status = mxl692_i2c_writeread(dev, + MXL_EAGLE_OPCODE_ATSC_ERROR_COUNTERS_GET, + NULL, + 0, + rx_buf, + sizeof(struct MXL_EAGLE_ATSC_DEMOD_ERROR_COUNTERS_T)); + if (!mxl_status) { + if (atsc_errors->error_packets == 0) + utmp = 0; + else + utmp = ((atsc_errors->error_bytes / atsc_errors->error_packets) * + atsc_errors->total_packets); + /* ber */ + c->post_bit_error.stat[0].scale = FE_SCALE_COUNTER; + c->post_bit_error.stat[0].uvalue += atsc_errors->error_bytes; + c->post_bit_count.stat[0].scale = FE_SCALE_COUNTER; + c->post_bit_count.stat[0].uvalue += utmp; + /* ucb */ + c->block_error.stat[0].scale = FE_SCALE_COUNTER; + c->block_error.stat[0].uvalue += atsc_errors->error_packets; + + dev_dbg(&dev->i2c_client->dev, "%llu %llu\n", + c->post_bit_count.stat[0].uvalue, c->block_error.stat[0].uvalue); + } + break; + case MXL_EAGLE_DEMOD_TYPE_QAM: + case MXL_EAGLE_DEMOD_TYPE_OOB: + default: + break; + } + + if (mxl_status) + dev_dbg(&dev->i2c_client->dev, "err %d\n", mxl_status); + + return mxl_status; +} + +static int mxl692_read_status(struct dvb_frontend *fe, + enum fe_status *status) +{ + struct mxl692_dev *dev = fe->demodulator_priv; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + u8 rx_buf[MXL_EAGLE_MAX_I2C_PACKET_SIZE] = {}; + struct MXL_EAGLE_ATSC_DEMOD_STATUS_T *atsc_status; + struct MXL_EAGLE_QAM_DEMOD_STATUS_T *qam_status; + enum MXL_EAGLE_DEMOD_TYPE_E demod_type = dev->demod_type; + int mxl_status = 0; + *status = 0; + + dev_dbg(&dev->i2c_client->dev, "\n"); + + atsc_status = (struct MXL_EAGLE_ATSC_DEMOD_STATUS_T *)&rx_buf; + qam_status = (struct MXL_EAGLE_QAM_DEMOD_STATUS_T *)&rx_buf; + + switch (demod_type) { + case MXL_EAGLE_DEMOD_TYPE_ATSC: + mxl_status = mxl692_i2c_writeread(dev, + MXL_EAGLE_OPCODE_ATSC_STATUS_GET, + NULL, + 0, + rx_buf, + sizeof(struct MXL_EAGLE_ATSC_DEMOD_STATUS_T)); + if (!mxl_status && atsc_status->atsc_lock) { + *status |= FE_HAS_SIGNAL; + *status |= FE_HAS_CARRIER; + *status |= FE_HAS_VITERBI; + *status |= FE_HAS_SYNC; + *status |= FE_HAS_LOCK; + + c->cnr.stat[0].scale = FE_SCALE_DECIBEL; + c->cnr.stat[0].svalue = atsc_status->snr_db_tenths / 10; + } + break; + case MXL_EAGLE_DEMOD_TYPE_QAM: + mxl_status = mxl692_i2c_writeread(dev, + MXL_EAGLE_OPCODE_QAM_STATUS_GET, + NULL, + 0, + rx_buf, + sizeof(struct MXL_EAGLE_QAM_DEMOD_STATUS_T)); + if (!mxl_status && qam_status->qam_locked) { + *status |= FE_HAS_SIGNAL; + *status |= FE_HAS_CARRIER; + *status |= FE_HAS_VITERBI; + *status |= FE_HAS_SYNC; + *status |= FE_HAS_LOCK; + + c->cnr.stat[0].scale = FE_SCALE_DECIBEL; + c->cnr.stat[0].svalue = qam_status->snr_db_tenths / 10; + } + break; + case MXL_EAGLE_DEMOD_TYPE_OOB: + default: + break; + } + + if ((*status & FE_HAS_LOCK) == 0) { + /* No lock, reset all statistics */ + c->cnr.len = 1; + c->cnr.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->block_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->post_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->post_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + return 0; + } + + if (mxl_status) + dev_dbg(&dev->i2c_client->dev, "err %d\n", mxl_status); + else + mxl_status = mxl692_read_ber_ucb(fe); + + return mxl_status; +} + +static const struct dvb_frontend_ops mxl692_ops = { + .delsys = { SYS_ATSC }, + .info = { + .name = "MaxLinear MxL692 VSB tuner-demodulator", + .frequency_min_hz = 54000000, + .frequency_max_hz = 858000000, + .frequency_stepsize_hz = 62500, + .caps = FE_CAN_8VSB + }, + + .init = mxl692_init, + .sleep = mxl692_sleep, + .set_frontend = mxl692_set_frontend, + .get_frontend = mxl692_get_frontend, + + .read_status = mxl692_read_status, + .read_snr = mxl692_read_snr, +}; + +static int mxl692_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mxl692_config *config = client->dev.platform_data; + struct mxl692_dev *dev; + int ret = 0; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + ret = -ENOMEM; + dev_dbg(&client->dev, "kzalloc() failed\n"); + goto err; + } + + memcpy(&dev->fe.ops, &mxl692_ops, sizeof(struct dvb_frontend_ops)); + dev->fe.demodulator_priv = dev; + dev->i2c_client = client; + *config->fe = &dev->fe; + mutex_init(&dev->i2c_lock); + i2c_set_clientdata(client, dev); + + dev_info(&client->dev, "MaxLinear mxl692 successfully attached\n"); + + return 0; +err: + dev_dbg(&client->dev, "failed %d\n", ret); + return -ENODEV; +} + +static int mxl692_remove(struct i2c_client *client) +{ + struct mxl692_dev *dev = i2c_get_clientdata(client); + + dev->fe.demodulator_priv = NULL; + i2c_set_clientdata(client, NULL); + kfree(dev); + + return 0; +} + +static const struct i2c_device_id mxl692_id_table[] = { + {"mxl692", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, mxl692_id_table); + +static struct i2c_driver mxl692_driver = { + .driver = { + .name = "mxl692", + }, + .probe = mxl692_probe, + .remove = mxl692_remove, + .id_table = mxl692_id_table, +}; + +module_i2c_driver(mxl692_driver); + +MODULE_AUTHOR("Brad Love <brad@nextdimension.cc>"); +MODULE_DESCRIPTION("MaxLinear MxL692 demodulator/tuner driver"); +MODULE_FIRMWARE(MXL692_FIRMWARE); +MODULE_LICENSE("GPL"); |