| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2018 Gateworks Corporation |
| */ |
| #include <linux/delay.h> |
| #include <linux/hdmi.h> |
| #include <linux/i2c.h> |
| #include <linux/init.h> |
| #include <linux/interrupt.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/of_graph.h> |
| #include <linux/platform_device.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/types.h> |
| #include <linux/v4l2-dv-timings.h> |
| #include <linux/videodev2.h> |
| |
| #include <media/v4l2-ctrls.h> |
| #include <media/v4l2-device.h> |
| #include <media/v4l2-dv-timings.h> |
| #include <media/v4l2-event.h> |
| #include <media/v4l2-fwnode.h> |
| #include <media/i2c/tda1997x.h> |
| |
| #include <sound/core.h> |
| #include <sound/pcm.h> |
| #include <sound/pcm_params.h> |
| #include <sound/soc.h> |
| |
| #include <dt-bindings/media/tda1997x.h> |
| |
| #include "tda1997x_regs.h" |
| |
| #define TDA1997X_MBUS_CODES 5 |
| |
| /* debug level */ |
| static int debug; |
| module_param(debug, int, 0644); |
| MODULE_PARM_DESC(debug, "debug level (0-2)"); |
| |
| /* Audio formats */ |
| static const char * const audtype_names[] = { |
| "PCM", /* PCM Samples */ |
| "HBR", /* High Bit Rate Audio */ |
| "OBA", /* One-Bit Audio */ |
| "DST" /* Direct Stream Transfer */ |
| }; |
| |
| /* Audio output port formats */ |
| enum audfmt_types { |
| AUDFMT_TYPE_DISABLED = 0, |
| AUDFMT_TYPE_I2S, |
| AUDFMT_TYPE_SPDIF, |
| }; |
| static const char * const audfmt_names[] = { |
| "Disabled", |
| "I2S", |
| "SPDIF", |
| }; |
| |
| /* Video input formats */ |
| static const char * const hdmi_colorspace_names[] = { |
| "RGB", "YUV422", "YUV444", "YUV420", "", "", "", "", |
| }; |
| static const char * const hdmi_colorimetry_names[] = { |
| "", "ITU601", "ITU709", "Extended", |
| }; |
| static const char * const v4l2_quantization_names[] = { |
| "Default", |
| "Full Range (0-255)", |
| "Limited Range (16-235)", |
| }; |
| |
| /* Video output port formats */ |
| static const char * const vidfmt_names[] = { |
| "RGB444/YUV444", /* RGB/YUV444 16bit data bus, 8bpp */ |
| "YUV422 semi-planar", /* YUV422 16bit data base, 8bpp */ |
| "YUV422 CCIR656", /* BT656 (YUV 8bpp 2 clock per pixel) */ |
| "Invalid", |
| }; |
| |
| /* |
| * Colorspace conversion matrices |
| */ |
| struct color_matrix_coefs { |
| const char *name; |
| /* Input offsets */ |
| s16 offint1; |
| s16 offint2; |
| s16 offint3; |
| /* Coeficients */ |
| s16 p11coef; |
| s16 p12coef; |
| s16 p13coef; |
| s16 p21coef; |
| s16 p22coef; |
| s16 p23coef; |
| s16 p31coef; |
| s16 p32coef; |
| s16 p33coef; |
| /* Output offsets */ |
| s16 offout1; |
| s16 offout2; |
| s16 offout3; |
| }; |
| |
| enum { |
| ITU709_RGBFULL, |
| ITU601_RGBFULL, |
| RGBLIMITED_RGBFULL, |
| RGBLIMITED_ITU601, |
| RGBLIMITED_ITU709, |
| RGBFULL_ITU601, |
| RGBFULL_ITU709, |
| }; |
| |
| /* NB: 4096 is 1.0 using fixed point numbers */ |
| static const struct color_matrix_coefs conv_matrix[] = { |
| { |
| "YUV709 -> RGB full", |
| -256, -2048, -2048, |
| 4769, -2183, -873, |
| 4769, 7343, 0, |
| 4769, 0, 8652, |
| 0, 0, 0, |
| }, |
| { |
| "YUV601 -> RGB full", |
| -256, -2048, -2048, |
| 4769, -3330, -1602, |
| 4769, 6538, 0, |
| 4769, 0, 8264, |
| 256, 256, 256, |
| }, |
| { |
| "RGB limited -> RGB full", |
| -256, -256, -256, |
| 0, 4769, 0, |
| 0, 0, 4769, |
| 4769, 0, 0, |
| 0, 0, 0, |
| }, |
| { |
| "RGB limited -> ITU601", |
| -256, -256, -256, |
| 2404, 1225, 467, |
| -1754, 2095, -341, |
| -1388, -707, 2095, |
| 256, 2048, 2048, |
| }, |
| { |
| "RGB limited -> ITU709", |
| -256, -256, -256, |
| 2918, 867, 295, |
| -1894, 2087, -190, |
| -1607, -477, 2087, |
| 256, 2048, 2048, |
| }, |
| { |
| "RGB full -> ITU601", |
| 0, 0, 0, |
| 2065, 1052, 401, |
| -1506, 1799, -293, |
| -1192, -607, 1799, |
| 256, 2048, 2048, |
| }, |
| { |
| "RGB full -> ITU709", |
| 0, 0, 0, |
| 2506, 745, 253, |
| -1627, 1792, -163, |
| -1380, -410, 1792, |
| 256, 2048, 2048, |
| }, |
| }; |
| |
| static const struct v4l2_dv_timings_cap tda1997x_dv_timings_cap = { |
| .type = V4L2_DV_BT_656_1120, |
| /* keep this initialization for compatibility with GCC < 4.4.6 */ |
| .reserved = { 0 }, |
| |
| V4L2_INIT_BT_TIMINGS( |
| 640, 1920, /* min/max width */ |
| 350, 1200, /* min/max height */ |
| 13000000, 165000000, /* min/max pixelclock */ |
| /* standards */ |
| V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | |
| V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, |
| /* capabilities */ |
| V4L2_DV_BT_CAP_INTERLACED | V4L2_DV_BT_CAP_PROGRESSIVE | |
| V4L2_DV_BT_CAP_REDUCED_BLANKING | |
| V4L2_DV_BT_CAP_CUSTOM |
| ) |
| }; |
| |
| /* regulator supplies */ |
| static const char * const tda1997x_supply_name[] = { |
| "DOVDD", /* Digital I/O supply */ |
| "DVDD", /* Digital Core supply */ |
| "AVDD", /* Analog supply */ |
| }; |
| |
| #define TDA1997X_NUM_SUPPLIES ARRAY_SIZE(tda1997x_supply_name) |
| |
| enum tda1997x_type { |
| TDA19971, |
| TDA19973, |
| }; |
| |
| enum tda1997x_hdmi_pads { |
| TDA1997X_PAD_SOURCE, |
| TDA1997X_NUM_PADS, |
| }; |
| |
| struct tda1997x_chip_info { |
| enum tda1997x_type type; |
| const char *name; |
| }; |
| |
| struct tda1997x_state { |
| const struct tda1997x_chip_info *info; |
| struct tda1997x_platform_data pdata; |
| struct i2c_client *client; |
| struct i2c_client *client_cec; |
| struct v4l2_subdev sd; |
| struct regulator_bulk_data supplies[TDA1997X_NUM_SUPPLIES]; |
| struct media_pad pads[TDA1997X_NUM_PADS]; |
| struct mutex lock; |
| struct mutex page_lock; |
| char page; |
| |
| /* detected info from chip */ |
| int chip_revision; |
| char port_30bit; |
| char output_2p5; |
| char tmdsb_clk; |
| char tmdsb_soc; |
| |
| /* status info */ |
| char hdmi_status; |
| char mptrw_in_progress; |
| char activity_status; |
| char input_detect[2]; |
| |
| /* video */ |
| struct hdmi_avi_infoframe avi_infoframe; |
| struct v4l2_hdmi_colorimetry colorimetry; |
| u32 rgb_quantization_range; |
| struct v4l2_dv_timings timings; |
| int fps; |
| const struct color_matrix_coefs *conv; |
| u32 mbus_codes[TDA1997X_MBUS_CODES]; /* available modes */ |
| u32 mbus_code; /* current mode */ |
| u8 vid_fmt; |
| |
| /* controls */ |
| struct v4l2_ctrl_handler hdl; |
| struct v4l2_ctrl *detect_tx_5v_ctrl; |
| struct v4l2_ctrl *rgb_quantization_range_ctrl; |
| |
| /* audio */ |
| u8 audio_ch_alloc; |
| int audio_samplerate; |
| int audio_channels; |
| int audio_samplesize; |
| int audio_type; |
| struct mutex audio_lock; |
| struct snd_pcm_substream *audio_stream; |
| |
| /* EDID */ |
| struct { |
| u8 edid[256]; |
| u32 present; |
| unsigned int blocks; |
| } edid; |
| struct delayed_work delayed_work_enable_hpd; |
| }; |
| |
| static const struct v4l2_event tda1997x_ev_fmt = { |
| .type = V4L2_EVENT_SOURCE_CHANGE, |
| .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, |
| }; |
| |
| static const struct tda1997x_chip_info tda1997x_chip_info[] = { |
| [TDA19971] = { |
| .type = TDA19971, |
| .name = "tda19971", |
| }, |
| [TDA19973] = { |
| .type = TDA19973, |
| .name = "tda19973", |
| }, |
| }; |
| |
| static inline struct tda1997x_state *to_state(struct v4l2_subdev *sd) |
| { |
| return container_of(sd, struct tda1997x_state, sd); |
| } |
| |
| static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) |
| { |
| return &container_of(ctrl->handler, struct tda1997x_state, hdl)->sd; |
| } |
| |
| static int tda1997x_cec_read(struct v4l2_subdev *sd, u8 reg) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| int val; |
| |
| val = i2c_smbus_read_byte_data(state->client_cec, reg); |
| if (val < 0) { |
| v4l_err(state->client, "read reg error: reg=%2x\n", reg); |
| val = -1; |
| } |
| |
| return val; |
| } |
| |
| static int tda1997x_cec_write(struct v4l2_subdev *sd, u8 reg, u8 val) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| int ret = 0; |
| |
| ret = i2c_smbus_write_byte_data(state->client_cec, reg, val); |
| if (ret < 0) { |
| v4l_err(state->client, "write reg error:reg=%2x,val=%2x\n", |
| reg, val); |
| ret = -1; |
| } |
| |
| return ret; |
| } |
| |
| /* ----------------------------------------------------------------------------- |
| * I2C transfer |
| */ |
| |
| static int tda1997x_setpage(struct v4l2_subdev *sd, u8 page) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| int ret; |
| |
| if (state->page != page) { |
| ret = i2c_smbus_write_byte_data(state->client, |
| REG_CURPAGE_00H, page); |
| if (ret < 0) { |
| v4l_err(state->client, |
| "write reg error:reg=%2x,val=%2x\n", |
| REG_CURPAGE_00H, page); |
| return ret; |
| } |
| state->page = page; |
| } |
| return 0; |
| } |
| |
| static inline int io_read(struct v4l2_subdev *sd, u16 reg) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| int val; |
| |
| mutex_lock(&state->page_lock); |
| if (tda1997x_setpage(sd, reg >> 8)) { |
| val = -1; |
| goto out; |
| } |
| |
| val = i2c_smbus_read_byte_data(state->client, reg&0xff); |
| if (val < 0) { |
| v4l_err(state->client, "read reg error: reg=%2x\n", reg & 0xff); |
| val = -1; |
| goto out; |
| } |
| |
| out: |
| mutex_unlock(&state->page_lock); |
| return val; |
| } |
| |
| static inline long io_read16(struct v4l2_subdev *sd, u16 reg) |
| { |
| int val; |
| long lval = 0; |
| |
| val = io_read(sd, reg); |
| if (val < 0) |
| return val; |
| lval |= (val << 8); |
| val = io_read(sd, reg + 1); |
| if (val < 0) |
| return val; |
| lval |= val; |
| |
| return lval; |
| } |
| |
| static inline long io_read24(struct v4l2_subdev *sd, u16 reg) |
| { |
| int val; |
| long lval = 0; |
| |
| val = io_read(sd, reg); |
| if (val < 0) |
| return val; |
| lval |= (val << 16); |
| val = io_read(sd, reg + 1); |
| if (val < 0) |
| return val; |
| lval |= (val << 8); |
| val = io_read(sd, reg + 2); |
| if (val < 0) |
| return val; |
| lval |= val; |
| |
| return lval; |
| } |
| |
| static unsigned int io_readn(struct v4l2_subdev *sd, u16 reg, u8 len, u8 *data) |
| { |
| int i; |
| int sz = 0; |
| int val; |
| |
| for (i = 0; i < len; i++) { |
| val = io_read(sd, reg + i); |
| if (val < 0) |
| break; |
| data[i] = val; |
| sz++; |
| } |
| |
| return sz; |
| } |
| |
| static int io_write(struct v4l2_subdev *sd, u16 reg, u8 val) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| s32 ret = 0; |
| |
| mutex_lock(&state->page_lock); |
| if (tda1997x_setpage(sd, reg >> 8)) { |
| ret = -1; |
| goto out; |
| } |
| |
| ret = i2c_smbus_write_byte_data(state->client, reg & 0xff, val); |
| if (ret < 0) { |
| v4l_err(state->client, "write reg error:reg=%2x,val=%2x\n", |
| reg&0xff, val); |
| ret = -1; |
| goto out; |
| } |
| |
| out: |
| mutex_unlock(&state->page_lock); |
| return ret; |
| } |
| |
| static int io_write16(struct v4l2_subdev *sd, u16 reg, u16 val) |
| { |
| int ret; |
| |
| ret = io_write(sd, reg, (val >> 8) & 0xff); |
| if (ret < 0) |
| return ret; |
| ret = io_write(sd, reg + 1, val & 0xff); |
| if (ret < 0) |
| return ret; |
| return 0; |
| } |
| |
| static int io_write24(struct v4l2_subdev *sd, u16 reg, u32 val) |
| { |
| int ret; |
| |
| ret = io_write(sd, reg, (val >> 16) & 0xff); |
| if (ret < 0) |
| return ret; |
| ret = io_write(sd, reg + 1, (val >> 8) & 0xff); |
| if (ret < 0) |
| return ret; |
| ret = io_write(sd, reg + 2, val & 0xff); |
| if (ret < 0) |
| return ret; |
| return 0; |
| } |
| |
| /* ----------------------------------------------------------------------------- |
| * Hotplug |
| */ |
| |
| enum hpd_mode { |
| HPD_LOW_BP, /* HPD low and pulse of at least 100ms */ |
| HPD_LOW_OTHER, /* HPD low and pulse of at least 100ms */ |
| HPD_HIGH_BP, /* HIGH */ |
| HPD_HIGH_OTHER, |
| HPD_PULSE, /* HPD low pulse */ |
| }; |
| |
| /* manual HPD (Hot Plug Detect) control */ |
| static int tda1997x_manual_hpd(struct v4l2_subdev *sd, enum hpd_mode mode) |
| { |
| u8 hpd_auto, hpd_pwr, hpd_man; |
| |
| hpd_auto = io_read(sd, REG_HPD_AUTO_CTRL); |
| hpd_pwr = io_read(sd, REG_HPD_POWER); |
| hpd_man = io_read(sd, REG_HPD_MAN_CTRL); |
| |
| /* mask out unused bits */ |
| hpd_man &= (HPD_MAN_CTRL_HPD_PULSE | |
| HPD_MAN_CTRL_5VEN | |
| HPD_MAN_CTRL_HPD_B | |
| HPD_MAN_CTRL_HPD_A); |
| |
| switch (mode) { |
| /* HPD low and pulse of at least 100ms */ |
| case HPD_LOW_BP: |
| /* hpd_bp=0 */ |
| hpd_pwr &= ~HPD_POWER_BP_MASK; |
| /* disable HPD_A and HPD_B */ |
| hpd_man &= ~(HPD_MAN_CTRL_HPD_A | HPD_MAN_CTRL_HPD_B); |
| io_write(sd, REG_HPD_POWER, hpd_pwr); |
| io_write(sd, REG_HPD_MAN_CTRL, hpd_man); |
| break; |
| /* HPD high */ |
| case HPD_HIGH_BP: |
| /* hpd_bp=1 */ |
| hpd_pwr &= ~HPD_POWER_BP_MASK; |
| hpd_pwr |= 1 << HPD_POWER_BP_SHIFT; |
| io_write(sd, REG_HPD_POWER, hpd_pwr); |
| break; |
| /* HPD low and pulse of at least 100ms */ |
| case HPD_LOW_OTHER: |
| /* disable HPD_A and HPD_B */ |
| hpd_man &= ~(HPD_MAN_CTRL_HPD_A | HPD_MAN_CTRL_HPD_B); |
| /* hp_other=0 */ |
| hpd_auto &= ~HPD_AUTO_HP_OTHER; |
| io_write(sd, REG_HPD_AUTO_CTRL, hpd_auto); |
| io_write(sd, REG_HPD_MAN_CTRL, hpd_man); |
| break; |
| /* HPD high */ |
| case HPD_HIGH_OTHER: |
| hpd_auto |= HPD_AUTO_HP_OTHER; |
| io_write(sd, REG_HPD_AUTO_CTRL, hpd_auto); |
| break; |
| /* HPD low pulse */ |
| case HPD_PULSE: |
| /* disable HPD_A and HPD_B */ |
| hpd_man &= ~(HPD_MAN_CTRL_HPD_A | HPD_MAN_CTRL_HPD_B); |
| io_write(sd, REG_HPD_MAN_CTRL, hpd_man); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static void tda1997x_delayed_work_enable_hpd(struct work_struct *work) |
| { |
| struct delayed_work *dwork = to_delayed_work(work); |
| struct tda1997x_state *state = container_of(dwork, |
| struct tda1997x_state, |
| delayed_work_enable_hpd); |
| struct v4l2_subdev *sd = &state->sd; |
| |
| v4l2_dbg(2, debug, sd, "%s\n", __func__); |
| |
| /* Set HPD high */ |
| tda1997x_manual_hpd(sd, HPD_HIGH_OTHER); |
| tda1997x_manual_hpd(sd, HPD_HIGH_BP); |
| |
| state->edid.present = 1; |
| } |
| |
| static void tda1997x_disable_edid(struct v4l2_subdev *sd) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| |
| v4l2_dbg(1, debug, sd, "%s\n", __func__); |
| cancel_delayed_work_sync(&state->delayed_work_enable_hpd); |
| |
| /* Set HPD low */ |
| tda1997x_manual_hpd(sd, HPD_LOW_BP); |
| } |
| |
| static void tda1997x_enable_edid(struct v4l2_subdev *sd) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| |
| v4l2_dbg(1, debug, sd, "%s\n", __func__); |
| |
| /* Enable hotplug after 100ms */ |
| schedule_delayed_work(&state->delayed_work_enable_hpd, HZ / 10); |
| } |
| |
| /* ----------------------------------------------------------------------------- |
| * Signal Control |
| */ |
| |
| /* |
| * configure vid_fmt based on mbus_code |
| */ |
| static int |
| tda1997x_setup_format(struct tda1997x_state *state, u32 code) |
| { |
| v4l_dbg(1, debug, state->client, "%s code=0x%x\n", __func__, code); |
| switch (code) { |
| case MEDIA_BUS_FMT_RGB121212_1X36: |
| case MEDIA_BUS_FMT_RGB888_1X24: |
| case MEDIA_BUS_FMT_YUV12_1X36: |
| case MEDIA_BUS_FMT_YUV8_1X24: |
| state->vid_fmt = OF_FMT_444; |
| break; |
| case MEDIA_BUS_FMT_UYVY12_1X24: |
| case MEDIA_BUS_FMT_UYVY10_1X20: |
| case MEDIA_BUS_FMT_UYVY8_1X16: |
| state->vid_fmt = OF_FMT_422_SMPT; |
| break; |
| case MEDIA_BUS_FMT_UYVY12_2X12: |
| case MEDIA_BUS_FMT_UYVY10_2X10: |
| case MEDIA_BUS_FMT_UYVY8_2X8: |
| state->vid_fmt = OF_FMT_422_CCIR; |
| break; |
| default: |
| v4l_err(state->client, "incompatible format (0x%x)\n", code); |
| return -EINVAL; |
| } |
| v4l_dbg(1, debug, state->client, "%s code=0x%x fmt=%s\n", __func__, |
| code, vidfmt_names[state->vid_fmt]); |
| state->mbus_code = code; |
| |
| return 0; |
| } |
| |
| /* |
| * The color conversion matrix will convert between the colorimetry of the |
| * HDMI input to the desired output format RGB|YUV. RGB output is to be |
| * full-range and YUV is to be limited range. |
| * |
| * RGB full-range uses values from 0 to 255 which is recommended on a monitor |
| * and RGB Limited uses values from 16 to 236 (16=black, 235=white) which is |
| * typically recommended on a TV. |
| */ |
| static void |
| tda1997x_configure_csc(struct v4l2_subdev *sd) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| struct hdmi_avi_infoframe *avi = &state->avi_infoframe; |
| struct v4l2_hdmi_colorimetry *c = &state->colorimetry; |
| /* Blanking code values depend on output colorspace (RGB or YUV) */ |
| struct blanking_codes { |
| s16 code_gy; |
| s16 code_bu; |
| s16 code_rv; |
| }; |
| static const struct blanking_codes rgb_blanking = { 64, 64, 64 }; |
| static const struct blanking_codes yuv_blanking = { 64, 512, 512 }; |
| const struct blanking_codes *blanking_codes = NULL; |
| u8 reg; |
| |
| v4l_dbg(1, debug, state->client, "input:%s quant:%s output:%s\n", |
| hdmi_colorspace_names[avi->colorspace], |
| v4l2_quantization_names[c->quantization], |
| vidfmt_names[state->vid_fmt]); |
| state->conv = NULL; |
| switch (state->vid_fmt) { |
| /* RGB output */ |
| case OF_FMT_444: |
| blanking_codes = &rgb_blanking; |
| if (c->colorspace == V4L2_COLORSPACE_SRGB) { |
| if (c->quantization == V4L2_QUANTIZATION_LIM_RANGE) |
| state->conv = &conv_matrix[RGBLIMITED_RGBFULL]; |
| } else { |
| if (c->colorspace == V4L2_COLORSPACE_REC709) |
| state->conv = &conv_matrix[ITU709_RGBFULL]; |
| else if (c->colorspace == V4L2_COLORSPACE_SMPTE170M) |
| state->conv = &conv_matrix[ITU601_RGBFULL]; |
| } |
| break; |
| |
| /* YUV output */ |
| case OF_FMT_422_SMPT: /* semi-planar */ |
| case OF_FMT_422_CCIR: /* CCIR656 */ |
| blanking_codes = &yuv_blanking; |
| if ((c->colorspace == V4L2_COLORSPACE_SRGB) && |
| (c->quantization == V4L2_QUANTIZATION_FULL_RANGE)) { |
| if (state->timings.bt.height <= 576) |
| state->conv = &conv_matrix[RGBFULL_ITU601]; |
| else |
| state->conv = &conv_matrix[RGBFULL_ITU709]; |
| } else if ((c->colorspace == V4L2_COLORSPACE_SRGB) && |
| (c->quantization == V4L2_QUANTIZATION_LIM_RANGE)) { |
| if (state->timings.bt.height <= 576) |
| state->conv = &conv_matrix[RGBLIMITED_ITU601]; |
| else |
| state->conv = &conv_matrix[RGBLIMITED_ITU709]; |
| } |
| break; |
| } |
| |
| if (state->conv) { |
| v4l_dbg(1, debug, state->client, "%s\n", |
| state->conv->name); |
| /* enable matrix conversion */ |
| reg = io_read(sd, REG_VDP_CTRL); |
| reg &= ~VDP_CTRL_MATRIX_BP; |
| io_write(sd, REG_VDP_CTRL, reg); |
| /* offset inputs */ |
| io_write16(sd, REG_VDP_MATRIX + 0, state->conv->offint1); |
| io_write16(sd, REG_VDP_MATRIX + 2, state->conv->offint2); |
| io_write16(sd, REG_VDP_MATRIX + 4, state->conv->offint3); |
| /* coefficients */ |
| io_write16(sd, REG_VDP_MATRIX + 6, state->conv->p11coef); |
| io_write16(sd, REG_VDP_MATRIX + 8, state->conv->p12coef); |
| io_write16(sd, REG_VDP_MATRIX + 10, state->conv->p13coef); |
| io_write16(sd, REG_VDP_MATRIX + 12, state->conv->p21coef); |
| io_write16(sd, REG_VDP_MATRIX + 14, state->conv->p22coef); |
| io_write16(sd, REG_VDP_MATRIX + 16, state->conv->p23coef); |
| io_write16(sd, REG_VDP_MATRIX + 18, state->conv->p31coef); |
| io_write16(sd, REG_VDP_MATRIX + 20, state->conv->p32coef); |
| io_write16(sd, REG_VDP_MATRIX + 22, state->conv->p33coef); |
| /* offset outputs */ |
| io_write16(sd, REG_VDP_MATRIX + 24, state->conv->offout1); |
| io_write16(sd, REG_VDP_MATRIX + 26, state->conv->offout2); |
| io_write16(sd, REG_VDP_MATRIX + 28, state->conv->offout3); |
| } else { |
| /* disable matrix conversion */ |
| reg = io_read(sd, REG_VDP_CTRL); |
| reg |= VDP_CTRL_MATRIX_BP; |
| io_write(sd, REG_VDP_CTRL, reg); |
| } |
| |
| /* SetBlankingCodes */ |
| if (blanking_codes) { |
| io_write16(sd, REG_BLK_GY, blanking_codes->code_gy); |
| io_write16(sd, REG_BLK_BU, blanking_codes->code_bu); |
| io_write16(sd, REG_BLK_RV, blanking_codes->code_rv); |
| } |
| } |
| |
| /* Configure frame detection window and VHREF timing generator */ |
| static void |
| tda1997x_configure_vhref(struct v4l2_subdev *sd) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| const struct v4l2_bt_timings *bt = &state->timings.bt; |
| int width, lines; |
| u16 href_start, href_end; |
| u16 vref_f1_start, vref_f2_start; |
| u8 vref_f1_width, vref_f2_width; |
| u8 field_polarity; |
| u16 fieldref_f1_start, fieldref_f2_start; |
| u8 reg; |
| |
| href_start = bt->hbackporch + bt->hsync + 1; |
| href_end = href_start + bt->width; |
| vref_f1_start = bt->height + bt->vbackporch + bt->vsync + |
| bt->il_vbackporch + bt->il_vsync + |
| bt->il_vfrontporch; |
| vref_f1_width = bt->vbackporch + bt->vsync + bt->vfrontporch; |
| vref_f2_start = 0; |
| vref_f2_width = 0; |
| fieldref_f1_start = 0; |
| fieldref_f2_start = 0; |
| if (bt->interlaced) { |
| vref_f2_start = (bt->height / 2) + |
| (bt->il_vbackporch + bt->il_vsync - 1); |
| vref_f2_width = bt->il_vbackporch + bt->il_vsync + |
| bt->il_vfrontporch; |
| fieldref_f2_start = vref_f2_start + bt->il_vfrontporch + |
| fieldref_f1_start; |
| } |
| field_polarity = 0; |
| |
| width = V4L2_DV_BT_FRAME_WIDTH(bt); |
| lines = V4L2_DV_BT_FRAME_HEIGHT(bt); |
| |
| /* |
| * Configure Frame Detection Window: |
| * horiz area where the VHREF module consider a VSYNC a new frame |
| */ |
| io_write16(sd, REG_FDW_S, 0x2ef); /* start position */ |
| io_write16(sd, REG_FDW_E, 0x141); /* end position */ |
| |
| /* Set Pixel And Line Counters */ |
| if (state->chip_revision == 0) |
| io_write16(sd, REG_PXCNT_PR, 4); |
| else |
| io_write16(sd, REG_PXCNT_PR, 1); |
| io_write16(sd, REG_PXCNT_NPIX, width & MASK_VHREF); |
| io_write16(sd, REG_LCNT_PR, 1); |
| io_write16(sd, REG_LCNT_NLIN, lines & MASK_VHREF); |
| |
| /* |
| * Configure the VHRef timing generator responsible for rebuilding all |
| * horiz and vert synch and ref signals from its input allowing auto |
| * detection algorithms and forcing predefined modes (480i & 576i) |
| */ |
| reg = VHREF_STD_DET_OFF << VHREF_STD_DET_SHIFT; |
| io_write(sd, REG_VHREF_CTRL, reg); |
| |
| /* |
| * Configure the VHRef timing values. In case the VHREF generator has |
| * been configured in manual mode, this will allow to manually set all |
| * horiz and vert ref values (non-active pixel areas) of the generator |
| * and allows setting the frame reference params. |
| */ |
| /* horizontal reference start/end */ |
| io_write16(sd, REG_HREF_S, href_start & MASK_VHREF); |
| io_write16(sd, REG_HREF_E, href_end & MASK_VHREF); |
| /* vertical reference f1 start/end */ |
| io_write16(sd, REG_VREF_F1_S, vref_f1_start & MASK_VHREF); |
| io_write(sd, REG_VREF_F1_WIDTH, vref_f1_width); |
| /* vertical reference f2 start/end */ |
| io_write16(sd, REG_VREF_F2_S, vref_f2_start & MASK_VHREF); |
| io_write(sd, REG_VREF_F2_WIDTH, vref_f2_width); |
| |
| /* F1/F2 FREF, field polarity */ |
| reg = fieldref_f1_start & MASK_VHREF; |
| reg |= field_polarity << 8; |
| io_write16(sd, REG_FREF_F1_S, reg); |
| reg = fieldref_f2_start & MASK_VHREF; |
| io_write16(sd, REG_FREF_F2_S, reg); |
| } |
| |
| /* Configure Video Output port signals */ |
| static int |
| tda1997x_configure_vidout(struct tda1997x_state *state) |
| { |
| struct v4l2_subdev *sd = &state->sd; |
| struct tda1997x_platform_data *pdata = &state->pdata; |
| u8 prefilter; |
| u8 reg; |
| |
| /* Configure pixel clock generator: delay, polarity, rate */ |
| reg = (state->vid_fmt == OF_FMT_422_CCIR) ? |
| PCLK_SEL_X2 : PCLK_SEL_X1; |
| reg |= pdata->vidout_delay_pclk << PCLK_DELAY_SHIFT; |
| reg |= pdata->vidout_inv_pclk << PCLK_INV_SHIFT; |
| io_write(sd, REG_PCLK, reg); |
| |
| /* Configure pre-filter */ |
| prefilter = 0; /* filters off */ |
| /* YUV422 mode requires conversion */ |
| if ((state->vid_fmt == OF_FMT_422_SMPT) || |
| (state->vid_fmt == OF_FMT_422_CCIR)) { |
| /* 2/7 taps for Rv and Bu */ |
| prefilter = FILTERS_CTRL_2_7TAP << FILTERS_CTRL_BU_SHIFT | |
| FILTERS_CTRL_2_7TAP << FILTERS_CTRL_RV_SHIFT; |
| } |
| io_write(sd, REG_FILTERS_CTRL, prefilter); |
| |
| /* Configure video port */ |
| reg = state->vid_fmt & OF_FMT_MASK; |
| if (state->vid_fmt == OF_FMT_422_CCIR) |
| reg |= (OF_BLK | OF_TRC); |
| reg |= OF_VP_ENABLE; |
| io_write(sd, REG_OF, reg); |
| |
| /* Configure formatter and conversions */ |
| reg = io_read(sd, REG_VDP_CTRL); |
| /* pre-filter is needed unless (REG_FILTERS_CTRL == 0) */ |
| if (!prefilter) |
| reg |= VDP_CTRL_PREFILTER_BP; |
| else |
| reg &= ~VDP_CTRL_PREFILTER_BP; |
| /* formatter is needed for YUV422 and for trc/blc codes */ |
| if (state->vid_fmt == OF_FMT_444) |
| reg |= VDP_CTRL_FORMATTER_BP; |
| /* formatter and compdel needed for timing/blanking codes */ |
| else |
| reg &= ~(VDP_CTRL_FORMATTER_BP | VDP_CTRL_COMPDEL_BP); |
| /* activate compdel for small sync delays */ |
| if ((pdata->vidout_delay_vs < 4) || (pdata->vidout_delay_hs < 4)) |
| reg &= ~VDP_CTRL_COMPDEL_BP; |
| io_write(sd, REG_VDP_CTRL, reg); |
| |
| /* Configure DE output signal: delay, polarity, and source */ |
| reg = pdata->vidout_delay_de << DE_FREF_DELAY_SHIFT | |
| pdata->vidout_inv_de << DE_FREF_INV_SHIFT | |
| pdata->vidout_sel_de << DE_FREF_SEL_SHIFT; |
| io_write(sd, REG_DE_FREF, reg); |
| |
| /* Configure HS/HREF output signal: delay, polarity, and source */ |
| if (state->vid_fmt != OF_FMT_422_CCIR) { |
| reg = pdata->vidout_delay_hs << HS_HREF_DELAY_SHIFT | |
| pdata->vidout_inv_hs << HS_HREF_INV_SHIFT | |
| pdata->vidout_sel_hs << HS_HREF_SEL_SHIFT; |
| } else |
| reg = HS_HREF_SEL_NONE << HS_HREF_SEL_SHIFT; |
| io_write(sd, REG_HS_HREF, reg); |
| |
| /* Configure VS/VREF output signal: delay, polarity, and source */ |
| if (state->vid_fmt != OF_FMT_422_CCIR) { |
| reg = pdata->vidout_delay_vs << VS_VREF_DELAY_SHIFT | |
| pdata->vidout_inv_vs << VS_VREF_INV_SHIFT | |
| pdata->vidout_sel_vs << VS_VREF_SEL_SHIFT; |
| } else |
| reg = VS_VREF_SEL_NONE << VS_VREF_SEL_SHIFT; |
| io_write(sd, REG_VS_VREF, reg); |
| |
| return 0; |
| } |
| |
| /* Configure Audio output port signals */ |
| static int |
| tda1997x_configure_audout(struct v4l2_subdev *sd, u8 channel_assignment) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| struct tda1997x_platform_data *pdata = &state->pdata; |
| bool sp_used_by_fifo = true; |
| u8 reg; |
| |
| if (!pdata->audout_format) |
| return 0; |
| |
| /* channel assignment (CEA-861-D Table 20) */ |
| io_write(sd, REG_AUDIO_PATH, channel_assignment); |
| |
| /* Audio output configuration */ |
| reg = 0; |
| switch (pdata->audout_format) { |
| case AUDFMT_TYPE_I2S: |
| reg |= AUDCFG_BUS_I2S << AUDCFG_BUS_SHIFT; |
| break; |
| case AUDFMT_TYPE_SPDIF: |
| reg |= AUDCFG_BUS_SPDIF << AUDCFG_BUS_SHIFT; |
| break; |
| } |
| switch (state->audio_type) { |
| case AUDCFG_TYPE_PCM: |
| reg |= AUDCFG_TYPE_PCM << AUDCFG_TYPE_SHIFT; |
| break; |
| case AUDCFG_TYPE_OBA: |
| reg |= AUDCFG_TYPE_OBA << AUDCFG_TYPE_SHIFT; |
| break; |
| case AUDCFG_TYPE_DST: |
| reg |= AUDCFG_TYPE_DST << AUDCFG_TYPE_SHIFT; |
| sp_used_by_fifo = false; |
| break; |
| case AUDCFG_TYPE_HBR: |
| reg |= AUDCFG_TYPE_HBR << AUDCFG_TYPE_SHIFT; |
| if (pdata->audout_layout == 1) { |
| /* demuxed via AP0:AP3 */ |
| reg |= AUDCFG_HBR_DEMUX << AUDCFG_HBR_SHIFT; |
| if (pdata->audout_format == AUDFMT_TYPE_SPDIF) |
| sp_used_by_fifo = false; |
| } else { |
| /* straight via AP0 */ |
| reg |= AUDCFG_HBR_STRAIGHT << AUDCFG_HBR_SHIFT; |
| } |
| break; |
| } |
| if (pdata->audout_width == 32) |
| reg |= AUDCFG_I2SW_32 << AUDCFG_I2SW_SHIFT; |
| else |
| reg |= AUDCFG_I2SW_16 << AUDCFG_I2SW_SHIFT; |
| |
| /* automatic hardware mute */ |
| if (pdata->audio_auto_mute) |
| reg |= AUDCFG_AUTO_MUTE_EN; |
| /* clock polarity */ |
| if (pdata->audout_invert_clk) |
| reg |= AUDCFG_CLK_INVERT; |
| io_write(sd, REG_AUDCFG, reg); |
| |
| /* audio layout */ |
| reg = (pdata->audout_layout) ? AUDIO_LAYOUT_LAYOUT1 : 0; |
| if (!pdata->audout_layoutauto) |
| reg |= AUDIO_LAYOUT_MANUAL; |
| if (sp_used_by_fifo) |
| reg |= AUDIO_LAYOUT_SP_FLAG; |
| io_write(sd, REG_AUDIO_LAYOUT, reg); |
| |
| /* FIFO Latency value */ |
| io_write(sd, REG_FIFO_LATENCY_VAL, 0x80); |
| |
| /* Audio output port config */ |
| if (sp_used_by_fifo) { |
| reg = AUDIO_OUT_ENABLE_AP0; |
| if (channel_assignment >= 0x01) |
| reg |= AUDIO_OUT_ENABLE_AP1; |
| if (channel_assignment >= 0x04) |
| reg |= AUDIO_OUT_ENABLE_AP2; |
| if (channel_assignment >= 0x0c) |
| reg |= AUDIO_OUT_ENABLE_AP3; |
| /* specific cases where AP1 is not used */ |
| if ((channel_assignment == 0x04) |
| || (channel_assignment == 0x08) |
| || (channel_assignment == 0x0c) |
| || (channel_assignment == 0x10) |
| || (channel_assignment == 0x14) |
| || (channel_assignment == 0x18) |
| || (channel_assignment == 0x1c)) |
| reg &= ~AUDIO_OUT_ENABLE_AP1; |
| /* specific cases where AP2 is not used */ |
| if ((channel_assignment >= 0x14) |
| && (channel_assignment <= 0x17)) |
| reg &= ~AUDIO_OUT_ENABLE_AP2; |
| } else { |
| reg = AUDIO_OUT_ENABLE_AP3 | |
| AUDIO_OUT_ENABLE_AP2 | |
| AUDIO_OUT_ENABLE_AP1 | |
| AUDIO_OUT_ENABLE_AP0; |
| } |
| if (pdata->audout_format == AUDFMT_TYPE_I2S) |
| reg |= (AUDIO_OUT_ENABLE_ACLK | AUDIO_OUT_ENABLE_WS); |
| io_write(sd, REG_AUDIO_OUT_ENABLE, reg); |
| |
| /* reset test mode to normal audio freq auto selection */ |
| io_write(sd, REG_TEST_MODE, 0x00); |
| |
| return 0; |
| } |
| |
| /* Soft Reset of specific hdmi info */ |
| static int |
| tda1997x_hdmi_info_reset(struct v4l2_subdev *sd, u8 info_rst, bool reset_sus) |
| { |
| u8 reg; |
| |
| /* reset infoframe engine packets */ |
| reg = io_read(sd, REG_HDMI_INFO_RST); |
| io_write(sd, REG_HDMI_INFO_RST, info_rst); |
| |
| /* if infoframe engine has been reset clear INT_FLG_MODE */ |
| if (reg & RESET_IF) { |
| reg = io_read(sd, REG_INT_FLG_CLR_MODE); |
| io_write(sd, REG_INT_FLG_CLR_MODE, reg); |
| } |
| |
| /* Disable REFTIM to restart start-up-sequencer (SUS) */ |
| reg = io_read(sd, REG_RATE_CTRL); |
| reg &= ~RATE_REFTIM_ENABLE; |
| if (!reset_sus) |
| reg |= RATE_REFTIM_ENABLE; |
| reg = io_write(sd, REG_RATE_CTRL, reg); |
| |
| return 0; |
| } |
| |
| static void |
| tda1997x_power_mode(struct tda1997x_state *state, bool enable) |
| { |
| struct v4l2_subdev *sd = &state->sd; |
| u8 reg; |
| |
| if (enable) { |
| /* Automatic control of TMDS */ |
| io_write(sd, REG_PON_OVR_EN, PON_DIS); |
| /* Enable current bias unit */ |
| io_write(sd, REG_CFG1, PON_EN); |
| /* Enable deep color PLL */ |
| io_write(sd, REG_DEEP_PLL7_BYP, PON_DIS); |
| /* Output buffers active */ |
| reg = io_read(sd, REG_OF); |
| reg &= ~OF_VP_ENABLE; |
| io_write(sd, REG_OF, reg); |
| } else { |
| /* Power down EDID mode sequence */ |
| /* Output buffers in HiZ */ |
| reg = io_read(sd, REG_OF); |
| reg |= OF_VP_ENABLE; |
| io_write(sd, REG_OF, reg); |
| /* Disable deep color PLL */ |
| io_write(sd, REG_DEEP_PLL7_BYP, PON_EN); |
| /* Disable current bias unit */ |
| io_write(sd, REG_CFG1, PON_DIS); |
| /* Manual control of TMDS */ |
| io_write(sd, REG_PON_OVR_EN, PON_EN); |
| } |
| } |
| |
| static bool |
| tda1997x_detect_tx_5v(struct v4l2_subdev *sd) |
| { |
| u8 reg = io_read(sd, REG_DETECT_5V); |
| |
| return ((reg & DETECT_5V_SEL) ? 1 : 0); |
| } |
| |
| static bool |
| tda1997x_detect_tx_hpd(struct v4l2_subdev *sd) |
| { |
| u8 reg = io_read(sd, REG_DETECT_5V); |
| |
| return ((reg & DETECT_HPD) ? 1 : 0); |
| } |
| |
| static int |
| tda1997x_detect_std(struct tda1997x_state *state, |
| struct v4l2_dv_timings *timings) |
| { |
| struct v4l2_subdev *sd = &state->sd; |
| |
| /* |
| * Read the FMT registers |
| * REG_V_PER: Period of a frame (or field) in MCLK (27MHz) cycles |
| * REG_H_PER: Period of a line in MCLK (27MHz) cycles |
| * REG_HS_WIDTH: Period of horiz sync pulse in MCLK (27MHz) cycles |
| */ |
| u32 vper, vsync_pos; |
| u16 hper, hsync_pos, hsper, interlaced; |
| u16 htot, hact, hfront, hsync, hback; |
| u16 vtot, vact, vfront1, vfront2, vsync, vback1, vback2; |
| |
| if (!state->input_detect[0] && !state->input_detect[1]) |
| return -ENOLINK; |
| |
| vper = io_read24(sd, REG_V_PER); |
| hper = io_read16(sd, REG_H_PER); |
| hsper = io_read16(sd, REG_HS_WIDTH); |
| vsync_pos = vper & MASK_VPER_SYNC_POS; |
| hsync_pos = hper & MASK_HPER_SYNC_POS; |
| interlaced = hsper & MASK_HSWIDTH_INTERLACED; |
| vper &= MASK_VPER; |
| hper &= MASK_HPER; |
| hsper &= MASK_HSWIDTH; |
| v4l2_dbg(1, debug, sd, "Signal Timings: %u/%u/%u\n", vper, hper, hsper); |
| |
| htot = io_read16(sd, REG_FMT_H_TOT); |
| hact = io_read16(sd, REG_FMT_H_ACT); |
| hfront = io_read16(sd, REG_FMT_H_FRONT); |
| hsync = io_read16(sd, REG_FMT_H_SYNC); |
| hback = io_read16(sd, REG_FMT_H_BACK); |
| |
| vtot = io_read16(sd, REG_FMT_V_TOT); |
| vact = io_read16(sd, REG_FMT_V_ACT); |
| vfront1 = io_read(sd, REG_FMT_V_FRONT_F1); |
| vfront2 = io_read(sd, REG_FMT_V_FRONT_F2); |
| vsync = io_read(sd, REG_FMT_V_SYNC); |
| vback1 = io_read(sd, REG_FMT_V_BACK_F1); |
| vback2 = io_read(sd, REG_FMT_V_BACK_F2); |
| |
| v4l2_dbg(1, debug, sd, "Geometry: H %u %u %u %u %u Sync%c V %u %u %u %u %u %u %u Sync%c\n", |
| htot, hact, hfront, hsync, hback, hsync_pos ? '+' : '-', |
| vtot, vact, vfront1, vfront2, vsync, vback1, vback2, vsync_pos ? '+' : '-'); |
| |
| if (!timings) |
| return 0; |
| |
| timings->type = V4L2_DV_BT_656_1120; |
| timings->bt.width = hact; |
| timings->bt.hfrontporch = hfront; |
| timings->bt.hsync = hsync; |
| timings->bt.hbackporch = hback; |
| timings->bt.height = vact; |
| timings->bt.vfrontporch = vfront1; |
| timings->bt.vsync = vsync; |
| timings->bt.vbackporch = vback1; |
| timings->bt.interlaced = interlaced ? V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE; |
| timings->bt.polarities = vsync_pos ? V4L2_DV_VSYNC_POS_POL : 0; |
| timings->bt.polarities |= hsync_pos ? V4L2_DV_HSYNC_POS_POL : 0; |
| |
| timings->bt.pixelclock = (u64)htot * vtot * 27000000; |
| if (interlaced) { |
| timings->bt.il_vfrontporch = vfront2; |
| timings->bt.il_vsync = timings->bt.vsync; |
| timings->bt.il_vbackporch = vback2; |
| do_div(timings->bt.pixelclock, vper * 2 /* full frame */); |
| } else { |
| timings->bt.il_vfrontporch = 0; |
| timings->bt.il_vsync = 0; |
| timings->bt.il_vbackporch = 0; |
| do_div(timings->bt.pixelclock, vper); |
| } |
| v4l2_find_dv_timings_cap(timings, &tda1997x_dv_timings_cap, |
| (u32)timings->bt.pixelclock / 500, NULL, NULL); |
| v4l2_print_dv_timings(sd->name, "Detected format: ", timings, false); |
| return 0; |
| } |
| |
| /* some sort of errata workaround for chip revision 0 (N1) */ |
| static void tda1997x_reset_n1(struct tda1997x_state *state) |
| { |
| struct v4l2_subdev *sd = &state->sd; |
| u8 reg; |
| |
| /* clear HDMI mode flag in BCAPS */ |
| io_write(sd, REG_CLK_CFG, CLK_CFG_SEL_ACLK_EN | CLK_CFG_SEL_ACLK); |
| io_write(sd, REG_PON_OVR_EN, PON_EN); |
| io_write(sd, REG_PON_CBIAS, PON_EN); |
| io_write(sd, REG_PON_PLL, PON_EN); |
| |
| reg = io_read(sd, REG_MODE_REC_CFG1); |
| reg &= ~0x06; |
| reg |= 0x02; |
| io_write(sd, REG_MODE_REC_CFG1, reg); |
| io_write(sd, REG_CLK_CFG, CLK_CFG_DIS); |
| io_write(sd, REG_PON_OVR_EN, PON_DIS); |
| reg = io_read(sd, REG_MODE_REC_CFG1); |
| reg &= ~0x06; |
| io_write(sd, REG_MODE_REC_CFG1, reg); |
| } |
| |
| /* |
| * Activity detection must only be notified when stable_clk_x AND active_x |
| * bits are set to 1. If only stable_clk_x bit is set to 1 but not |
| * active_x, it means that the TMDS clock is not in the defined range |
| * and activity detection must not be notified. |
| */ |
| static u8 |
| tda1997x_read_activity_status_regs(struct v4l2_subdev *sd) |
| { |
| u8 reg, status = 0; |
| |
| /* Read CLK_A_STATUS register */ |
| reg = io_read(sd, REG_CLK_A_STATUS); |
| /* ignore if not active */ |
| if ((reg & MASK_CLK_STABLE) && !(reg & MASK_CLK_ACTIVE)) |
| reg &= ~MASK_CLK_STABLE; |
| status |= ((reg & MASK_CLK_STABLE) >> 2); |
| |
| /* Read CLK_B_STATUS register */ |
| reg = io_read(sd, REG_CLK_B_STATUS); |
| /* ignore if not active */ |
| if ((reg & MASK_CLK_STABLE) && !(reg & MASK_CLK_ACTIVE)) |
| reg &= ~MASK_CLK_STABLE; |
| status |= ((reg & MASK_CLK_STABLE) >> 1); |
| |
| /* Read the SUS_STATUS register */ |
| reg = io_read(sd, REG_SUS_STATUS); |
| |
| /* If state = 5 => TMDS is locked */ |
| if ((reg & MASK_SUS_STATUS) == LAST_STATE_REACHED) |
| status |= MASK_SUS_STATE; |
| else |
| status &= ~MASK_SUS_STATE; |
| |
| return status; |
| } |
| |
| static void |
| set_rgb_quantization_range(struct tda1997x_state *state) |
| { |
| struct v4l2_hdmi_colorimetry *c = &state->colorimetry; |
| |
| state->colorimetry = v4l2_hdmi_rx_colorimetry(&state->avi_infoframe, |
| NULL, |
| state->timings.bt.height); |
| /* If ycbcr_enc is V4L2_YCBCR_ENC_DEFAULT, we receive RGB */ |
| if (c->ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT) { |
| switch (state->rgb_quantization_range) { |
| case V4L2_DV_RGB_RANGE_LIMITED: |
| c->quantization = V4L2_QUANTIZATION_FULL_RANGE; |
| break; |
| case V4L2_DV_RGB_RANGE_FULL: |
| c->quantization = V4L2_QUANTIZATION_LIM_RANGE; |
| break; |
| } |
| } |
| v4l_dbg(1, debug, state->client, |
| "colorspace=%d/%d colorimetry=%d range=%s content=%d\n", |
| state->avi_infoframe.colorspace, c->colorspace, |
| state->avi_infoframe.colorimetry, |
| v4l2_quantization_names[c->quantization], |
| state->avi_infoframe.content_type); |
| } |
| |
| /* parse an infoframe and do some sanity checks on it */ |
| static unsigned int |
| tda1997x_parse_infoframe(struct tda1997x_state *state, u16 addr) |
| { |
| struct v4l2_subdev *sd = &state->sd; |
| union hdmi_infoframe frame; |
| u8 buffer[40] = { 0 }; |
| u8 reg; |
| int len, err; |
| |
| /* read data */ |
| len = io_readn(sd, addr, sizeof(buffer), buffer); |
| err = hdmi_infoframe_unpack(&frame, buffer, len); |
| if (err) { |
| v4l_err(state->client, |
| "failed parsing %d byte infoframe: 0x%04x/0x%02x\n", |
| len, addr, buffer[0]); |
| return err; |
| } |
| hdmi_infoframe_log(KERN_INFO, &state->client->dev, &frame); |
| switch (frame.any.type) { |
| /* Audio InfoFrame: see HDMI spec 8.2.2 */ |
| case HDMI_INFOFRAME_TYPE_AUDIO: |
| /* sample rate */ |
| switch (frame.audio.sample_frequency) { |
| case HDMI_AUDIO_SAMPLE_FREQUENCY_32000: |
| state->audio_samplerate = 32000; |
| break; |
| case HDMI_AUDIO_SAMPLE_FREQUENCY_44100: |
| state->audio_samplerate = 44100; |
| break; |
| case HDMI_AUDIO_SAMPLE_FREQUENCY_48000: |
| state->audio_samplerate = 48000; |
| break; |
| case HDMI_AUDIO_SAMPLE_FREQUENCY_88200: |
| state->audio_samplerate = 88200; |
| break; |
| case HDMI_AUDIO_SAMPLE_FREQUENCY_96000: |
| state->audio_samplerate = 96000; |
| break; |
| case HDMI_AUDIO_SAMPLE_FREQUENCY_176400: |
| state->audio_samplerate = 176400; |
| break; |
| case HDMI_AUDIO_SAMPLE_FREQUENCY_192000: |
| state->audio_samplerate = 192000; |
| break; |
| default: |
| case HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM: |
| break; |
| } |
| |
| /* sample size */ |
| switch (frame.audio.sample_size) { |
| case HDMI_AUDIO_SAMPLE_SIZE_16: |
| state->audio_samplesize = 16; |
| break; |
| case HDMI_AUDIO_SAMPLE_SIZE_20: |
| state->audio_samplesize = 20; |
| break; |
| case HDMI_AUDIO_SAMPLE_SIZE_24: |
| state->audio_samplesize = 24; |
| break; |
| case HDMI_AUDIO_SAMPLE_SIZE_STREAM: |
| default: |
| break; |
| } |
| |
| /* Channel Count */ |
| state->audio_channels = frame.audio.channels; |
| if (frame.audio.channel_allocation && |
| frame.audio.channel_allocation != state->audio_ch_alloc) { |
| /* use the channel assignment from the infoframe */ |
| state->audio_ch_alloc = frame.audio.channel_allocation; |
| tda1997x_configure_audout(sd, state->audio_ch_alloc); |
| /* reset the audio FIFO */ |
| tda1997x_hdmi_info_reset(sd, RESET_AUDIO, false); |
| } |
| break; |
| |
| /* Auxiliary Video information (AVI) InfoFrame: see HDMI spec 8.2.1 */ |
| case HDMI_INFOFRAME_TYPE_AVI: |
| state->avi_infoframe = frame.avi; |
| set_rgb_quantization_range(state); |
| |
| /* configure upsampler: 0=bypass 1=repeatchroma 2=interpolate */ |
| reg = io_read(sd, REG_PIX_REPEAT); |
| reg &= ~PIX_REPEAT_MASK_UP_SEL; |
| if (frame.avi.colorspace == HDMI_COLORSPACE_YUV422) |
| reg |= (PIX_REPEAT_CHROMA << PIX_REPEAT_SHIFT); |
| io_write(sd, REG_PIX_REPEAT, reg); |
| |
| /* ConfigurePixelRepeater: repeat n-times each pixel */ |
| reg = io_read(sd, REG_PIX_REPEAT); |
| reg &= ~PIX_REPEAT_MASK_REP; |
| reg |= frame.avi.pixel_repeat; |
| io_write(sd, REG_PIX_REPEAT, reg); |
| |
| /* configure the receiver with the new colorspace */ |
| tda1997x_configure_csc(sd); |
| break; |
| default: |
| break; |
| } |
| return 0; |
| } |
| |
| static void tda1997x_irq_sus(struct tda1997x_state *state, u8 *flags) |
| { |
| struct v4l2_subdev *sd = &state->sd; |
| u8 reg, source; |
| |
| source = io_read(sd, REG_INT_FLG_CLR_SUS); |
| io_write(sd, REG_INT_FLG_CLR_SUS, source); |
| |
| if (source & MASK_MPT) { |
| /* reset MTP in use flag if set */ |
| if (state->mptrw_in_progress) |
| state->mptrw_in_progress = 0; |
| } |
| |
| if (source & MASK_SUS_END) { |
| /* reset audio FIFO */ |
| reg = io_read(sd, REG_HDMI_INFO_RST); |
| reg |= MASK_SR_FIFO_FIFO_CTRL; |
| io_write(sd, REG_HDMI_INFO_RST, reg); |
| reg &= ~MASK_SR_FIFO_FIFO_CTRL; |
| io_write(sd, REG_HDMI_INFO_RST, reg); |
| |
| /* reset HDMI flags */ |
| state->hdmi_status = 0; |
| } |
| |
| /* filter FMT interrupt based on SUS state */ |
| reg = io_read(sd, REG_SUS_STATUS); |
| if (((reg & MASK_SUS_STATUS) != LAST_STATE_REACHED) |
| || (source & MASK_MPT)) { |
| source &= ~MASK_FMT; |
| } |
| |
| if (source & (MASK_FMT | MASK_SUS_END)) { |
| reg = io_read(sd, REG_SUS_STATUS); |
| if ((reg & MASK_SUS_STATUS) != LAST_STATE_REACHED) { |
| v4l_err(state->client, "BAD SUS STATUS\n"); |
| return; |
| } |
| if (debug) |
| tda1997x_detect_std(state, NULL); |
| /* notify user of change in resolution */ |
| v4l2_subdev_notify_event(&state->sd, &tda1997x_ev_fmt); |
| } |
| } |
| |
| static void tda1997x_irq_ddc(struct tda1997x_state *state, u8 *flags) |
| { |
| struct v4l2_subdev *sd = &state->sd; |
| u8 source; |
| |
| source = io_read(sd, REG_INT_FLG_CLR_DDC); |
| io_write(sd, REG_INT_FLG_CLR_DDC, source); |
| if (source & MASK_EDID_MTP) { |
| /* reset MTP in use flag if set */ |
| if (state->mptrw_in_progress) |
| state->mptrw_in_progress = 0; |
| } |
| |
| /* Detection of +5V */ |
| if (source & MASK_DET_5V) { |
| v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl, |
| tda1997x_detect_tx_5v(sd)); |
| } |
| } |
| |
| static void tda1997x_irq_rate(struct tda1997x_state *state, u8 *flags) |
| { |
| struct v4l2_subdev *sd = &state->sd; |
| u8 reg, source; |
| |
| u8 irq_status; |
| |
| source = io_read(sd, REG_INT_FLG_CLR_RATE); |
| io_write(sd, REG_INT_FLG_CLR_RATE, source); |
| |
| /* read status regs */ |
| irq_status = tda1997x_read_activity_status_regs(sd); |
| |
| /* |
| * read clock status reg until INT_FLG_CLR_RATE is still 0 |
| * after the read to make sure its the last one |
| */ |
| reg = source; |
| while (reg != 0) { |
| irq_status = tda1997x_read_activity_status_regs(sd); |
| reg = io_read(sd, REG_INT_FLG_CLR_RATE); |
| io_write(sd, REG_INT_FLG_CLR_RATE, reg); |
| source |= reg; |
| } |
| |
| /* we only pay attention to stability change events */ |
| if (source & (MASK_RATE_A_ST | MASK_RATE_B_ST)) { |
| int input = (source & MASK_RATE_A_ST)?0:1; |
| u8 mask = 1<<input; |
| |
| /* state change */ |
| if ((irq_status & mask) != (state->activity_status & mask)) { |
| /* activity lost */ |
| if ((irq_status & mask) == 0) { |
| v4l_info(state->client, |
| "HDMI-%c: Digital Activity Lost\n", |
| input+'A'); |
| |
| /* bypass up/down sampler and pixel repeater */ |
| reg = io_read(sd, REG_PIX_REPEAT); |
| reg &= ~PIX_REPEAT_MASK_UP_SEL; |
| reg &= ~PIX_REPEAT_MASK_REP; |
| io_write(sd, REG_PIX_REPEAT, reg); |
| |
| if (state->chip_revision == 0) |
| tda1997x_reset_n1(state); |
| |
| state->input_detect[input] = 0; |
| v4l2_subdev_notify_event(sd, &tda1997x_ev_fmt); |
| } |
| |
| /* activity detected */ |
| else { |
| v4l_info(state->client, |
| "HDMI-%c: Digital Activity Detected\n", |
| input+'A'); |
| state->input_detect[input] = 1; |
| } |
| |
| /* hold onto current state */ |
| state->activity_status = (irq_status & mask); |
| } |
| } |
| } |
| |
| static void tda1997x_irq_info(struct tda1997x_state *state, u8 *flags) |
| { |
| struct v4l2_subdev *sd = &state->sd; |
| u8 source; |
| |
| source = io_read(sd, REG_INT_FLG_CLR_INFO); |
| io_write(sd, REG_INT_FLG_CLR_INFO, source); |
| |
| /* Audio infoframe */ |
| if (source & MASK_AUD_IF) { |
| tda1997x_parse_infoframe(state, AUD_IF); |
| source &= ~MASK_AUD_IF; |
| } |
| |
| /* Source Product Descriptor infoframe change */ |
| if (source & MASK_SPD_IF) { |
| tda1997x_parse_infoframe(state, SPD_IF); |
| source &= ~MASK_SPD_IF; |
| } |
| |
| /* Auxiliary Video Information infoframe */ |
| if (source & MASK_AVI_IF) { |
| tda1997x_parse_infoframe(state, AVI_IF); |
| source &= ~MASK_AVI_IF; |
| } |
| } |
| |
| static void tda1997x_irq_audio(struct tda1997x_state *state, u8 *flags) |
| { |
| struct v4l2_subdev *sd = &state->sd; |
| u8 reg, source; |
| |
| source = io_read(sd, REG_INT_FLG_CLR_AUDIO); |
| io_write(sd, REG_INT_FLG_CLR_AUDIO, source); |
| |
| /* reset audio FIFO on FIFO pointer error or audio mute */ |
| if (source & MASK_ERROR_FIFO_PT || |
| source & MASK_MUTE_FLG) { |
| /* audio reset audio FIFO */ |
| reg = io_read(sd, REG_SUS_STATUS); |
| if ((reg & MASK_SUS_STATUS) == LAST_STATE_REACHED) { |
| reg = io_read(sd, REG_HDMI_INFO_RST); |
| reg |= MASK_SR_FIFO_FIFO_CTRL; |
| io_write(sd, REG_HDMI_INFO_RST, reg); |
| reg &= ~MASK_SR_FIFO_FIFO_CTRL; |
| io_write(sd, REG_HDMI_INFO_RST, reg); |
| /* reset channel status IT if present */ |
| source &= ~(MASK_CH_STATE); |
| } |
| } |
| if (source & MASK_AUDIO_FREQ_FLG) { |
| static const int freq[] = { |
| 0, 32000, 44100, 48000, 88200, 96000, 176400, 192000 |
| }; |
| |
| reg = io_read(sd, REG_AUDIO_FREQ); |
| state->audio_samplerate = freq[reg & 7]; |
| v4l_info(state->client, "Audio Frequency Change: %dHz\n", |
| state->audio_samplerate); |
| } |
| if (source & MASK_AUDIO_FLG) { |
| reg = io_read(sd, REG_AUDIO_FLAGS); |
| if (reg & BIT(AUDCFG_TYPE_DST)) |
| state->audio_type = AUDCFG_TYPE_DST; |
| if (reg & BIT(AUDCFG_TYPE_OBA)) |
| state->audio_type = AUDCFG_TYPE_OBA; |
| if (reg & BIT(AUDCFG_TYPE_HBR)) |
| state->audio_type = AUDCFG_TYPE_HBR; |
| if (reg & BIT(AUDCFG_TYPE_PCM)) |
| state->audio_type = AUDCFG_TYPE_PCM; |
| v4l_info(state->client, "Audio Type: %s\n", |
| audtype_names[state->audio_type]); |
| } |
| } |
| |
| static void tda1997x_irq_hdcp(struct tda1997x_state *state, u8 *flags) |
| { |
| struct v4l2_subdev *sd = &state->sd; |
| u8 reg, source; |
| |
| source = io_read(sd, REG_INT_FLG_CLR_HDCP); |
| io_write(sd, REG_INT_FLG_CLR_HDCP, source); |
| |
| /* reset MTP in use flag if set */ |
| if (source & MASK_HDCP_MTP) |
| state->mptrw_in_progress = 0; |
| if (source & MASK_STATE_C5) { |
| /* REPEATER: mask AUDIO and IF irqs to avoid IF during auth */ |
| reg = io_read(sd, REG_INT_MASK_TOP); |
| reg &= ~(INTERRUPT_AUDIO | INTERRUPT_INFO); |
| io_write(sd, REG_INT_MASK_TOP, reg); |
| *flags &= (INTERRUPT_AUDIO | INTERRUPT_INFO); |
| } |
| } |
| |
| static irqreturn_t tda1997x_isr_thread(int irq, void *d) |
| { |
| struct tda1997x_state *state = d; |
| struct v4l2_subdev *sd = &state->sd; |
| u8 flags; |
| |
| mutex_lock(&state->lock); |
| do { |
| /* read interrupt flags */ |
| flags = io_read(sd, REG_INT_FLG_CLR_TOP); |
| if (flags == 0) |
| break; |
| |
| /* SUS interrupt source (Input activity events) */ |
| if (flags & INTERRUPT_SUS) |
| tda1997x_irq_sus(state, &flags); |
| /* DDC interrupt source (Display Data Channel) */ |
| else if (flags & INTERRUPT_DDC) |
| tda1997x_irq_ddc(state, &flags); |
| /* RATE interrupt source (Digital Input activity) */ |
| else if (flags & INTERRUPT_RATE) |
| tda1997x_irq_rate(state, &flags); |
| /* Infoframe change interrupt */ |
| else if (flags & INTERRUPT_INFO) |
| tda1997x_irq_info(state, &flags); |
| /* Audio interrupt source: |
| * freq change, DST,OBA,HBR,ASP flags, mute, FIFO err |
| */ |
| else if (flags & INTERRUPT_AUDIO) |
| tda1997x_irq_audio(state, &flags); |
| /* HDCP interrupt source (content protection) */ |
| if (flags & INTERRUPT_HDCP) |
| tda1997x_irq_hdcp(state, &flags); |
| } while (flags != 0); |
| mutex_unlock(&state->lock); |
| |
| return IRQ_HANDLED; |
| } |
| |
| /* ----------------------------------------------------------------------------- |
| * v4l2_subdev_video_ops |
| */ |
| |
| static int |
| tda1997x_g_input_status(struct v4l2_subdev *sd, u32 *status) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| u32 vper; |
| u16 hper; |
| u16 hsper; |
| |
| mutex_lock(&state->lock); |
| vper = io_read24(sd, REG_V_PER) & MASK_VPER; |
| hper = io_read16(sd, REG_H_PER) & MASK_HPER; |
| hsper = io_read16(sd, REG_HS_WIDTH) & MASK_HSWIDTH; |
| /* |
| * The tda1997x supports A/B inputs but only a single output. |
| * The irq handler monitors for timing changes on both inputs and |
| * sets the input_detect array to 0|1 depending on signal presence. |
| * I believe selection of A vs B is automatic. |
| * |
| * The vper/hper/hsper registers provide the frame period, line period |
| * and horiz sync period (units of MCLK clock cycles (27MHz)) and |
| * testing shows these values to be random if no signal is present |
| * or locked. |
| */ |
| v4l2_dbg(1, debug, sd, "inputs:%d/%d timings:%d/%d/%d\n", |
| state->input_detect[0], state->input_detect[1], |
| vper, hper, hsper); |
| if (!state->input_detect[0] && !state->input_detect[1]) |
| *status = V4L2_IN_ST_NO_SIGNAL; |
| else if (!vper || !hper || !hsper) |
| *status = V4L2_IN_ST_NO_SYNC; |
| else |
| *status = 0; |
| mutex_unlock(&state->lock); |
| |
| return 0; |
| }; |
| |
| static int tda1997x_s_dv_timings(struct v4l2_subdev *sd, |
| struct v4l2_dv_timings *timings) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| |
| v4l_dbg(1, debug, state->client, "%s\n", __func__); |
| |
| if (v4l2_match_dv_timings(&state->timings, timings, 0, false)) |
| return 0; /* no changes */ |
| |
| if (!v4l2_valid_dv_timings(timings, &tda1997x_dv_timings_cap, |
| NULL, NULL)) |
| return -ERANGE; |
| |
| mutex_lock(&state->lock); |
| state->timings = *timings; |
| /* setup frame detection window and VHREF timing generator */ |
| tda1997x_configure_vhref(sd); |
| /* configure colorspace conversion */ |
| tda1997x_configure_csc(sd); |
| mutex_unlock(&state->lock); |
| |
| return 0; |
| } |
| |
| static int tda1997x_g_dv_timings(struct v4l2_subdev *sd, |
| struct v4l2_dv_timings *timings) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| |
| v4l_dbg(1, debug, state->client, "%s\n", __func__); |
| mutex_lock(&state->lock); |
| *timings = state->timings; |
| mutex_unlock(&state->lock); |
| |
| return 0; |
| } |
| |
| static int tda1997x_query_dv_timings(struct v4l2_subdev *sd, |
| struct v4l2_dv_timings *timings) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| int ret; |
| |
| v4l_dbg(1, debug, state->client, "%s\n", __func__); |
| memset(timings, 0, sizeof(struct v4l2_dv_timings)); |
| mutex_lock(&state->lock); |
| ret = tda1997x_detect_std(state, timings); |
| mutex_unlock(&state->lock); |
| |
| return ret; |
| } |
| |
| static const struct v4l2_subdev_video_ops tda1997x_video_ops = { |
| .g_input_status = tda1997x_g_input_status, |
| .s_dv_timings = tda1997x_s_dv_timings, |
| .g_dv_timings = tda1997x_g_dv_timings, |
| .query_dv_timings = tda1997x_query_dv_timings, |
| }; |
| |
| |
| /* ----------------------------------------------------------------------------- |
| * v4l2_subdev_pad_ops |
| */ |
| |
| static int tda1997x_init_state(struct v4l2_subdev *sd, |
| struct v4l2_subdev_state *sd_state) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| struct v4l2_mbus_framefmt *mf; |
| |
| mf = v4l2_subdev_state_get_format(sd_state, 0); |
| mf->code = state->mbus_codes[0]; |
| |
| return 0; |
| } |
| |
| static int tda1997x_enum_mbus_code(struct v4l2_subdev *sd, |
| struct v4l2_subdev_state *sd_state, |
| struct v4l2_subdev_mbus_code_enum *code) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| |
| v4l_dbg(1, debug, state->client, "%s %d\n", __func__, code->index); |
| if (code->index >= ARRAY_SIZE(state->mbus_codes)) |
| return -EINVAL; |
| |
| if (!state->mbus_codes[code->index]) |
| return -EINVAL; |
| |
| code->code = state->mbus_codes[code->index]; |
| |
| return 0; |
| } |
| |
| static void tda1997x_fill_format(struct tda1997x_state *state, |
| struct v4l2_mbus_framefmt *format) |
| { |
| const struct v4l2_bt_timings *bt; |
| |
| memset(format, 0, sizeof(*format)); |
| bt = &state->timings.bt; |
| format->width = bt->width; |
| format->height = bt->height; |
| format->colorspace = state->colorimetry.colorspace; |
| format->field = (bt->interlaced) ? |
| V4L2_FIELD_SEQ_TB : V4L2_FIELD_NONE; |
| } |
| |
| static int tda1997x_get_format(struct v4l2_subdev *sd, |
| struct v4l2_subdev_state *sd_state, |
| struct v4l2_subdev_format *format) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| |
| v4l_dbg(1, debug, state->client, "%s pad=%d which=%d\n", |
| __func__, format->pad, format->which); |
| |
| tda1997x_fill_format(state, &format->format); |
| |
| if (format->which == V4L2_SUBDEV_FORMAT_TRY) { |
| struct v4l2_mbus_framefmt *fmt; |
| |
| fmt = v4l2_subdev_state_get_format(sd_state, format->pad); |
| format->format.code = fmt->code; |
| } else |
| format->format.code = state->mbus_code; |
| |
| return 0; |
| } |
| |
| static int tda1997x_set_format(struct v4l2_subdev *sd, |
| struct v4l2_subdev_state *sd_state, |
| struct v4l2_subdev_format *format) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| u32 code = 0; |
| int i; |
| |
| v4l_dbg(1, debug, state->client, "%s pad=%d which=%d fmt=0x%x\n", |
| __func__, format->pad, format->which, format->format.code); |
| |
| for (i = 0; i < ARRAY_SIZE(state->mbus_codes); i++) { |
| if (format->format.code == state->mbus_codes[i]) { |
| code = state->mbus_codes[i]; |
| break; |
| } |
| } |
| if (!code) |
| code = state->mbus_codes[0]; |
| |
| tda1997x_fill_format(state, &format->format); |
| format->format.code = code; |
| |
| if (format->which == V4L2_SUBDEV_FORMAT_TRY) { |
| struct v4l2_mbus_framefmt *fmt; |
| |
| fmt = v4l2_subdev_state_get_format(sd_state, format->pad); |
| *fmt = format->format; |
| } else { |
| int ret = tda1997x_setup_format(state, format->format.code); |
| |
| if (ret) |
| return ret; |
| /* mbus_code has changed - re-configure csc/vidout */ |
| tda1997x_configure_csc(sd); |
| tda1997x_configure_vidout(state); |
| } |
| |
| return 0; |
| } |
| |
| static int tda1997x_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| |
| v4l_dbg(1, debug, state->client, "%s pad=%d\n", __func__, edid->pad); |
| memset(edid->reserved, 0, sizeof(edid->reserved)); |
| |
| if (edid->start_block == 0 && edid->blocks == 0) { |
| edid->blocks = state->edid.blocks; |
| return 0; |
| } |
| |
| if (!state->edid.present) |
| return -ENODATA; |
| |
| if (edid->start_block >= state->edid.blocks) |
| return -EINVAL; |
| |
| if (edid->start_block + edid->blocks > state->edid.blocks) |
| edid->blocks = state->edid.blocks - edid->start_block; |
| |
| memcpy(edid->edid, state->edid.edid + edid->start_block * 128, |
| edid->blocks * 128); |
| |
| return 0; |
| } |
| |
| static int tda1997x_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| int i; |
| |
| v4l_dbg(1, debug, state->client, "%s pad=%d\n", __func__, edid->pad); |
| memset(edid->reserved, 0, sizeof(edid->reserved)); |
| |
| if (edid->start_block != 0) |
| return -EINVAL; |
| |
| if (edid->blocks == 0) { |
| state->edid.blocks = 0; |
| state->edid.present = 0; |
| tda1997x_disable_edid(sd); |
| return 0; |
| } |
| |
| if (edid->blocks > 2) { |
| edid->blocks = 2; |
| return -E2BIG; |
| } |
| |
| tda1997x_disable_edid(sd); |
| |
| /* write base EDID */ |
| for (i = 0; i < 128; i++) |
| io_write(sd, REG_EDID_IN_BYTE0 + i, edid->edid[i]); |
| |
| /* write CEA Extension */ |
| for (i = 0; i < 128; i++) |
| io_write(sd, REG_EDID_IN_BYTE128 + i, edid->edid[i+128]); |
| |
| /* store state */ |
| memcpy(state->edid.edid, edid->edid, 256); |
| state->edid.blocks = edid->blocks; |
| |
| tda1997x_enable_edid(sd); |
| |
| return 0; |
| } |
| |
| static int tda1997x_get_dv_timings_cap(struct v4l2_subdev *sd, |
| struct v4l2_dv_timings_cap *cap) |
| { |
| *cap = tda1997x_dv_timings_cap; |
| return 0; |
| } |
| |
| static int tda1997x_enum_dv_timings(struct v4l2_subdev *sd, |
| struct v4l2_enum_dv_timings *timings) |
| { |
| return v4l2_enum_dv_timings_cap(timings, &tda1997x_dv_timings_cap, |
| NULL, NULL); |
| } |
| |
| static const struct v4l2_subdev_pad_ops tda1997x_pad_ops = { |
| .enum_mbus_code = tda1997x_enum_mbus_code, |
| .get_fmt = tda1997x_get_format, |
| .set_fmt = tda1997x_set_format, |
| .get_edid = tda1997x_get_edid, |
| .set_edid = tda1997x_set_edid, |
| .dv_timings_cap = tda1997x_get_dv_timings_cap, |
| .enum_dv_timings = tda1997x_enum_dv_timings, |
| }; |
| |
| /* ----------------------------------------------------------------------------- |
| * v4l2_subdev_core_ops |
| */ |
| |
| static int tda1997x_log_infoframe(struct v4l2_subdev *sd, int addr) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| union hdmi_infoframe frame; |
| u8 buffer[40] = { 0 }; |
| int len, err; |
| |
| /* read data */ |
| len = io_readn(sd, addr, sizeof(buffer), buffer); |
| v4l2_dbg(1, debug, sd, "infoframe: addr=%d len=%d\n", addr, len); |
| err = hdmi_infoframe_unpack(&frame, buffer, len); |
| if (err) { |
| v4l_err(state->client, |
| "failed parsing %d byte infoframe: 0x%04x/0x%02x\n", |
| len, addr, buffer[0]); |
| return err; |
| } |
| hdmi_infoframe_log(KERN_INFO, &state->client->dev, &frame); |
| |
| return 0; |
| } |
| |
| static int tda1997x_log_status(struct v4l2_subdev *sd) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| struct v4l2_dv_timings timings; |
| struct hdmi_avi_infoframe *avi = &state->avi_infoframe; |
| |
| v4l2_info(sd, "-----Chip status-----\n"); |
| v4l2_info(sd, "Chip: %s N%d\n", state->info->name, |
| state->chip_revision + 1); |
| v4l2_info(sd, "EDID Enabled: %s\n", state->edid.present ? "yes" : "no"); |
| |
| v4l2_info(sd, "-----Signal status-----\n"); |
| v4l2_info(sd, "Cable detected (+5V power): %s\n", |
| tda1997x_detect_tx_5v(sd) ? "yes" : "no"); |
| v4l2_info(sd, "HPD detected: %s\n", |
| tda1997x_detect_tx_hpd(sd) ? "yes" : "no"); |
| |
| v4l2_info(sd, "-----Video Timings-----\n"); |
| switch (tda1997x_detect_std(state, &timings)) { |
| case -ENOLINK: |
| v4l2_info(sd, "No video detected\n"); |
| break; |
| case -ERANGE: |
| v4l2_info(sd, "Invalid signal detected\n"); |
| break; |
| } |
| v4l2_print_dv_timings(sd->name, "Configured format: ", |
| &state->timings, true); |
| |
| v4l2_info(sd, "-----Color space-----\n"); |
| v4l2_info(sd, "Input color space: %s %s %s", |
| hdmi_colorspace_names[avi->colorspace], |
| (avi->colorspace == HDMI_COLORSPACE_RGB) ? "" : |
| hdmi_colorimetry_names[avi->colorimetry], |
| v4l2_quantization_names[state->colorimetry.quantization]); |
| v4l2_info(sd, "Output color space: %s", |
| vidfmt_names[state->vid_fmt]); |
| v4l2_info(sd, "Color space conversion: %s", state->conv ? |
| state->conv->name : "None"); |
| |
| v4l2_info(sd, "-----Audio-----\n"); |
| if (state->audio_channels) { |
| v4l2_info(sd, "audio: %dch %dHz\n", state->audio_channels, |
| state->audio_samplerate); |
| } else { |
| v4l2_info(sd, "audio: none\n"); |
| } |
| |
| v4l2_info(sd, "-----Infoframes-----\n"); |
| tda1997x_log_infoframe(sd, AUD_IF); |
| tda1997x_log_infoframe(sd, SPD_IF); |
| tda1997x_log_infoframe(sd, AVI_IF); |
| |
| return 0; |
| } |
| |
| static int tda1997x_subscribe_event(struct v4l2_subdev *sd, |
| struct v4l2_fh *fh, |
| struct v4l2_event_subscription *sub) |
| { |
| switch (sub->type) { |
| case V4L2_EVENT_SOURCE_CHANGE: |
| return v4l2_src_change_event_subdev_subscribe(sd, fh, sub); |
| case V4L2_EVENT_CTRL: |
| return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub); |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static const struct v4l2_subdev_core_ops tda1997x_core_ops = { |
| .log_status = tda1997x_log_status, |
| .subscribe_event = tda1997x_subscribe_event, |
| .unsubscribe_event = v4l2_event_subdev_unsubscribe, |
| }; |
| |
| /* ----------------------------------------------------------------------------- |
| * v4l2_subdev_ops |
| */ |
| |
| static const struct v4l2_subdev_ops tda1997x_subdev_ops = { |
| .core = &tda1997x_core_ops, |
| .video = &tda1997x_video_ops, |
| .pad = &tda1997x_pad_ops, |
| }; |
| |
| static const struct v4l2_subdev_internal_ops tda1997x_internal_ops = { |
| .init_state = tda1997x_init_state, |
| }; |
| |
| /* ----------------------------------------------------------------------------- |
| * v4l2_controls |
| */ |
| |
| static int tda1997x_s_ctrl(struct v4l2_ctrl *ctrl) |
| { |
| struct v4l2_subdev *sd = to_sd(ctrl); |
| struct tda1997x_state *state = to_state(sd); |
| |
| switch (ctrl->id) { |
| /* allow overriding the default RGB quantization range */ |
| case V4L2_CID_DV_RX_RGB_RANGE: |
| state->rgb_quantization_range = ctrl->val; |
| set_rgb_quantization_range(state); |
| tda1997x_configure_csc(sd); |
| return 0; |
| } |
| |
| return -EINVAL; |
| }; |
| |
| static int tda1997x_g_volatile_ctrl(struct v4l2_ctrl *ctrl) |
| { |
| struct v4l2_subdev *sd = to_sd(ctrl); |
| struct tda1997x_state *state = to_state(sd); |
| |
| if (ctrl->id == V4L2_CID_DV_RX_IT_CONTENT_TYPE) { |
| ctrl->val = state->avi_infoframe.content_type; |
| return 0; |
| } |
| return -EINVAL; |
| }; |
| |
| static const struct v4l2_ctrl_ops tda1997x_ctrl_ops = { |
| .s_ctrl = tda1997x_s_ctrl, |
| .g_volatile_ctrl = tda1997x_g_volatile_ctrl, |
| }; |
| |
| static int tda1997x_core_init(struct v4l2_subdev *sd) |
| { |
| struct tda1997x_state *state = to_state(sd); |
| struct tda1997x_platform_data *pdata = &state->pdata; |
| u8 reg; |
| int i; |
| |
| /* disable HPD */ |
| io_write(sd, REG_HPD_AUTO_CTRL, HPD_AUTO_HPD_UNSEL); |
| if (state->chip_revision == 0) { |
| io_write(sd, REG_MAN_SUS_HDMI_SEL, MAN_DIS_HDCP | MAN_RST_HDCP); |
| io_write(sd, REG_CGU_DBG_SEL, 1 << CGU_DBG_CLK_SEL_SHIFT); |
| } |
| |
| /* reset infoframe at end of start-up-sequencer */ |
| io_write(sd, REG_SUS_SET_RGB2, 0x06); |
| io_write(sd, REG_SUS_SET_RGB3, 0x06); |
| |
| /* Enable TMDS pull-ups */ |
| io_write(sd, REG_RT_MAN_CTRL, RT_MAN_CTRL_RT | |
| RT_MAN_CTRL_RT_B | RT_MAN_CTRL_RT_A); |
| |
| /* enable sync measurement timing */ |
| tda1997x_cec_write(sd, REG_PWR_CONTROL & 0xff, 0x04); |
| /* adjust CEC clock divider */ |
| tda1997x_cec_write(sd, REG_OSC_DIVIDER & 0xff, 0x03); |
| tda1997x_cec_write(sd, REG_EN_OSC_PERIOD_LSB & 0xff, 0xa0); |
| io_write(sd, REG_TIMER_D, 0x54); |
| /* enable power switch */ |
| reg = tda1997x_cec_read(sd, REG_CONTROL & 0xff); |
| reg |= 0x20; |
| tda1997x_cec_write(sd, REG_CONTROL & 0xff, reg); |
| mdelay(50); |
| |
| /* read the chip version */ |
| reg = io_read(sd, REG_VERSION); |
| /* get the chip configuration */ |
| reg = io_read(sd, REG_CMTP_REG10); |
| |
| /* enable interrupts we care about */ |
| io_write(sd, REG_INT_MASK_TOP, |
| INTERRUPT_HDCP | INTERRUPT_AUDIO | INTERRUPT_INFO | |
| INTERRUPT_RATE | INTERRUPT_SUS); |
| /* config_mtp,fmt,sus_end,sus_st */ |
| io_write(sd, REG_INT_MASK_SUS, MASK_MPT | MASK_FMT | MASK_SUS_END); |
| /* rate stability change for inputs A/B */ |
| io_write(sd, REG_INT_MASK_RATE, MASK_RATE_B_ST | MASK_RATE_A_ST); |
| /* aud,spd,avi*/ |
| io_write(sd, REG_INT_MASK_INFO, |
| MASK_AUD_IF | MASK_SPD_IF | MASK_AVI_IF); |
| /* audio_freq,audio_flg,mute_flg,fifo_err */ |
| io_write(sd, REG_INT_MASK_AUDIO, |
| MASK_AUDIO_FREQ_FLG | MASK_AUDIO_FLG | MASK_MUTE_FLG | |
| MASK_ERROR_FIFO_PT); |
| /* HDCP C5 state reached */ |
| io_write(sd, REG_INT_MASK_HDCP, MASK_STATE_C5); |
| /* 5V detect and HDP pulse end */ |
| io_write(sd, REG_INT_MASK_DDC, MASK_DET_5V); |
| /* don't care about AFE/MODE */ |
| io_write(sd, REG_INT_MASK_AFE, 0); |
| io_write(sd, REG_INT_MASK_MODE, 0); |
| |
| /* clear all interrupts */ |
| io_write(sd, REG_INT_FLG_CLR_TOP, 0xff); |
| io_write(sd, REG_INT_FLG_CLR_SUS, 0xff); |
| io_write(sd, REG_INT_FLG_CLR_DDC, 0xff); |
| io_write(sd, REG_INT_FLG_CLR_RATE, 0xff); |
| io_write(sd, REG_INT_FLG_CLR_MODE, 0xff); |
| io_write(sd, REG_INT_FLG_CLR_INFO, 0xff); |
| io_write(sd, REG_INT_FLG_CLR_AUDIO, 0xff); |
| io_write(sd, REG_INT_FLG_CLR_HDCP, 0xff); |
| io_write(sd, REG_INT_FLG_CLR_AFE, 0xff); |
| |
| /* init TMDS equalizer */ |
| if (state->chip_revision == 0) |
| io_write(sd, REG_CGU_DBG_SEL, 1 << CGU_DBG_CLK_SEL_SHIFT); |
| io_write24(sd, REG_CLK_MIN_RATE, CLK_MIN_RATE); |
| io_write24(sd, REG_CLK_MAX_RATE, CLK_MAX_RATE); |
| if (state->chip_revision == 0) |
| io_write(sd, REG_WDL_CFG, WDL_CFG_VAL); |
| /* DC filter */ |
| io_write(sd, REG_DEEP_COLOR_CTRL, DC_FILTER_VAL); |
| /* disable test pattern */ |
| io_write(sd, REG_SVC_MODE, 0x00); |
| /* update HDMI INFO CTRL */ |
| io_write(sd, REG_INFO_CTRL, 0xff); |
| /* write HDMI INFO EXCEED value */ |
| io_write(sd, REG_INFO_EXCEED, 3); |
| |
| if (state->chip_revision == 0) |
| tda1997x_reset_n1(state); |
| |
| /* |
| * No HDCP acknowledge when HDCP is disabled |
| * and reset SUS to force format detection |
| */ |
| tda1997x_hdmi_info_reset(sd, NACK_HDCP, true); |
| |
| /* Set HPD low */ |
| tda1997x_manual_hpd(sd, HPD_LOW_BP); |
| |
| /* Configure receiver capabilities */ |
| io_write(sd, REG_HDCP_BCAPS, HDCP_HDMI | HDCP_FAST_REAUTH); |
| |
| /* Configure HDMI: Auto HDCP mode, packet controlled mute */ |
| reg = HDMI_CTRL_MUTE_AUTO << HDMI_CTRL_MUTE_SHIFT; |
| reg |= HDMI_CTRL_HDCP_AUTO << HDMI_CTRL_HDCP_SHIFT; |
| io_write(sd, REG_HDMI_CTRL, reg); |
| |
| /* reset start-up-sequencer to force format detection */ |
| tda1997x_hdmi_info_reset(sd, 0, true); |
| |
| /* disable matrix conversion */ |
| reg = io_read(sd, REG_VDP_CTRL); |
| reg |= VDP_CTRL_MATRIX_BP; |
| io_write(sd, REG_VDP_CTRL, reg); |
| |
| /* set video output mode */ |
| tda1997x_configure_vidout(state); |
| |
| /* configure video output port */ |
| for (i = 0; i < 9; i++) { |
| v4l_dbg(1, debug, state->client, "vidout_cfg[%d]=0x%02x\n", i, |
| pdata->vidout_port_cfg[i]); |
| io_write(sd, REG_VP35_32_CTRL + i, pdata->vidout_port_cfg[i]); |
| } |
| |
| /* configure audio output port */ |
| tda1997x_configure_audout(sd, 0); |
| |
| /* configure audio clock freq */ |
| switch (pdata->audout_mclk_fs) { |
| case 512: |
| reg = AUDIO_CLOCK_SEL_512FS; |
| break; |
| case 256: |
| reg = AUDIO_CLOCK_SEL_256FS; |
| break; |
| case 128: |
| reg = AUDIO_CLOCK_SEL_128FS; |
| break; |
| case 64: |
| reg = AUDIO_CLOCK_SEL_64FS; |
| break; |
| case 32: |
| reg = AUDIO_CLOCK_SEL_32FS; |
| break; |
| default: |
| reg = AUDIO_CLOCK_SEL_16FS; |
| break; |
| } |
| io_write(sd, REG_AUDIO_CLOCK, reg); |
| |
| /* reset advanced infoframes (ISRC1/ISRC2/ACP) */ |
| tda1997x_hdmi_info_reset(sd, RESET_AI, false); |
| /* reset infoframe */ |
| tda1997x_hdmi_info_reset(sd, RESET_IF, false); |
| /* reset audio infoframes */ |
| tda1997x_hdmi_info_reset(sd, RESET_AUDIO, false); |
| /* reset gamut */ |
| tda1997x_hdmi_info_reset(sd, RESET_GAMUT, false); |
| |
| /* get initial HDMI status */ |
| state->hdmi_status = io_read(sd, REG_HDMI_FLAGS); |
| |
| io_write(sd, REG_EDID_ENABLE, EDID_ENABLE_A_EN | EDID_ENABLE_B_EN); |
| return 0; |
| } |
| |
| static int tda1997x_set_power(struct tda1997x_state *state, bool on) |
| { |
| int ret = 0; |
| |
| if (on) { |
| ret = regulator_bulk_enable(TDA1997X_NUM_SUPPLIES, |
| state->supplies); |
| msleep(300); |
| } else { |
| ret = regulator_bulk_disable(TDA1997X_NUM_SUPPLIES, |
| state->supplies); |
| } |
| |
| return ret; |
| } |
| |
| static const struct i2c_device_id tda1997x_i2c_id[] = { |
| {"tda19971", (kernel_ulong_t)&tda1997x_chip_info[TDA19971]}, |
| {"tda19973", (kernel_ulong_t)&tda1997x_chip_info[TDA19973]}, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(i2c, tda1997x_i2c_id); |
| |
| static const struct of_device_id tda1997x_of_id[] __maybe_unused = { |
| { .compatible = "nxp,tda19971", .data = &tda1997x_chip_info[TDA19971] }, |
| { .compatible = "nxp,tda19973", .data = &tda1997x_chip_info[TDA19973] }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(of, tda1997x_of_id); |
| |
| static int tda1997x_parse_dt(struct tda1997x_state *state) |
| { |
| struct tda1997x_platform_data *pdata = &state->pdata; |
| struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = 0 }; |
| struct device_node *ep; |
| struct device_node *np; |
| unsigned int flags; |
| const char *str; |
| int ret; |
| u32 v; |
| |
| /* |
| * setup default values: |
| * - HREF: active high from start to end of row |
| * - VS: Vertical Sync active high at beginning of frame |
| * - DE: Active high when data valid |
| * - A_CLK: 128*Fs |
| */ |
| pdata->vidout_sel_hs = HS_HREF_SEL_HREF_VHREF; |
| pdata->vidout_sel_vs = VS_VREF_SEL_VREF_HDMI; |
| pdata->vidout_sel_de = DE_FREF_SEL_DE_VHREF; |
| |
| np = state->client->dev.of_node; |
| ep = of_graph_get_endpoint_by_regs(np, 0, -1); |
| if (!ep) |
| return -EINVAL; |
| |
| ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &bus_cfg); |
| if (ret) { |
| of_node_put(ep); |
| return ret; |
| } |
| of_node_put(ep); |
| pdata->vidout_bus_type = bus_cfg.bus_type; |
| |
| /* polarity of HS/VS/DE */ |
| flags = bus_cfg.bus.parallel.flags; |
| if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) |
| pdata->vidout_inv_hs = 1; |
| if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) |
| pdata->vidout_inv_vs = 1; |
| if (flags & V4L2_MBUS_DATA_ACTIVE_LOW) |
| pdata->vidout_inv_de = 1; |
| pdata->vidout_bus_width = bus_cfg.bus.parallel.bus_width; |
| |
| /* video output port config */ |
| ret = of_property_count_u32_elems(np, "nxp,vidout-portcfg"); |
| if (ret > 0) { |
| u32 reg, val, i; |
| |
| for (i = 0; i < ret / 2 && i < 9; i++) { |
| of_property_read_u32_index(np, "nxp,vidout-portcfg", |
| i * 2, ®); |
| of_property_read_u32_index(np, "nxp,vidout-portcfg", |
| i * 2 + 1, &val); |
| if (reg < 9) |
| pdata->vidout_port_cfg[reg] = val; |
| } |
| } else { |
| v4l_err(state->client, "nxp,vidout-portcfg missing\n"); |
| return -EINVAL; |
| } |
| |
| /* default to channel layout dictated by packet header */ |
| pdata->audout_layoutauto = true; |
| |
| pdata->audout_format = AUDFMT_TYPE_DISABLED; |
| if (!of_property_read_string(np, "nxp,audout-format", &str)) { |
| if (strcmp(str, "i2s") == 0) |
| pdata->audout_format = AUDFMT_TYPE_I2S; |
| else if (strcmp(str, "spdif") == 0) |
| pdata->audout_format = AUDFMT_TYPE_SPDIF; |
| else { |
| v4l_err(state->client, "nxp,audout-format invalid\n"); |
| return -EINVAL; |
| } |
| if (!of_property_read_u32(np, "nxp,audout-layout", &v)) { |
| switch (v) { |
| case 0: |
| case 1: |
| break; |
| default: |
| v4l_err(state->client, |
| "nxp,audout-layout invalid\n"); |
| return -EINVAL; |
| } |
| pdata->audout_layout = v; |
| } |
| if (!of_property_read_u32(np, "nxp,audout-width", &v)) { |
| switch (v) { |
| case 16: |
| case 32: |
| break; |
| default: |
| v4l_err(state->client, |
| "nxp,audout-width invalid\n"); |
| return -EINVAL; |
| } |
| pdata->audout_width = v; |
| } |
| if (!of_property_read_u32(np, "nxp,audout-mclk-fs", &v)) { |
| switch (v) { |
| case 512: |
| case 256: |
| case 128: |
| case 64: |
| case 32: |
| case 16: |
| break; |
| default: |
| v4l_err(state->client, |
| "nxp,audout-mclk-fs invalid\n"); |
| return -EINVAL; |
| } |
| pdata->audout_mclk_fs = v; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int tda1997x_get_regulators(struct tda1997x_state *state) |
| { |
| int i; |
| |
| for (i = 0; i < TDA1997X_NUM_SUPPLIES; i++) |
| state->supplies[i].supply = tda1997x_supply_name[i]; |
| |
| return devm_regulator_bulk_get(&state->client->dev, |
| TDA1997X_NUM_SUPPLIES, |
| state->supplies); |
| } |
| |
| static int tda1997x_identify_module(struct tda1997x_state *state) |
| { |
| struct v4l2_subdev *sd = &state->sd; |
| enum tda1997x_type type; |
| u8 reg; |
| |
| /* Read chip configuration*/ |
| reg = io_read(sd, REG_CMTP_REG10); |
| state->tmdsb_clk = (reg >> 6) & 0x01; /* use tmds clock B_inv for B */ |
| state->tmdsb_soc = (reg >> 5) & 0x01; /* tmds of input B */ |
| state->port_30bit = (reg >> 2) & 0x03; /* 30bit vs 24bit */ |
| state->output_2p5 = (reg >> 1) & 0x01; /* output supply 2.5v */ |
| switch ((reg >> 4) & 0x03) { |
| case 0x00: |
| type = TDA19971; |
| break; |
| case 0x02: |
| case 0x03: |
| type = TDA19973; |
| break; |
| default: |
| dev_err(&state->client->dev, "unsupported chip ID\n"); |
| return -EIO; |
| } |
| if (state->info->type != type) { |
| dev_err(&state->client->dev, "chip id mismatch\n"); |
| return -EIO; |
| } |
| |
| /* read chip revision */ |
| state->chip_revision = io_read(sd, REG_CMTP_REG11); |
| |
| return 0; |
| } |
| |
| static const struct media_entity_operations tda1997x_media_ops = { |
| .link_validate = v4l2_subdev_link_validate, |
| }; |
| |
| |
| /* ----------------------------------------------------------------------------- |
| * HDMI Audio Codec |
| */ |
| |
| /* refine sample-rate based on HDMI source */ |
| static int tda1997x_pcm_startup(struct snd_pcm_substream *substream, |
| struct snd_soc_dai *dai) |
| { |
| struct v4l2_subdev *sd = snd_soc_dai_get_drvdata(dai); |
| struct tda1997x_state *state = to_state(sd); |
| struct snd_soc_component *component = dai->component; |
| struct snd_pcm_runtime *rtd = substream->runtime; |
| int rate, err; |
| |
| rate = state->audio_samplerate; |
| err = snd_pcm_hw_constraint_minmax(rtd, SNDRV_PCM_HW_PARAM_RATE, |
| rate, rate); |
| if (err < 0) { |
| dev_err(component->dev, "failed to constrain samplerate to %dHz\n", |
| rate); |
| return err; |
| } |
| dev_info(component->dev, "set samplerate constraint to %dHz\n", rate); |
| |
| return 0; |
| } |
| |
| static const struct snd_soc_dai_ops tda1997x_dai_ops = { |
| .startup = tda1997x_pcm_startup, |
| }; |
| |
| static struct snd_soc_dai_driver tda1997x_audio_dai = { |
| .name = "tda1997x", |
| .capture = { |
| .stream_name = "Capture", |
| .channels_min = 2, |
| .channels_max = 8, |
| .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | |
| SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | |
| SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | |
| SNDRV_PCM_RATE_192000, |
| }, |
| .ops = &tda1997x_dai_ops, |
| }; |
| |
| static int tda1997x_codec_probe(struct snd_soc_component *component) |
| { |
| return 0; |
| } |
| |
| static void tda1997x_codec_remove(struct snd_soc_component *component) |
| { |
| } |
| |
| static struct snd_soc_component_driver tda1997x_codec_driver = { |
| .probe = tda1997x_codec_probe, |
| .remove = tda1997x_codec_remove, |
| .idle_bias_on = 1, |
| .use_pmdown_time = 1, |
| .endianness = 1, |
| }; |
| |
| static int tda1997x_probe(struct i2c_client *client) |
| { |
| const struct i2c_device_id *id = i2c_client_get_device_id(client); |
| struct tda1997x_state *state; |
| struct tda1997x_platform_data *pdata; |
| struct v4l2_subdev *sd; |
| struct v4l2_ctrl_handler *hdl; |
| struct v4l2_ctrl *ctrl; |
| static const struct v4l2_dv_timings cea1920x1080 = |
| V4L2_DV_BT_CEA_1920X1080P60; |
| u32 *mbus_codes; |
| int i, ret; |
| |
| /* Check if the adapter supports the needed features */ |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) |
| return -EIO; |
| |
| state = kzalloc(sizeof(struct tda1997x_state), GFP_KERNEL); |
| if (!state) |
| return -ENOMEM; |
| |
| state->client = client; |
| pdata = &state->pdata; |
| if (IS_ENABLED(CONFIG_OF) && client->dev.of_node) { |
| const struct of_device_id *oid; |
| |
| oid = of_match_node(tda1997x_of_id, client->dev.of_node); |
| state->info = oid->data; |
| |
| ret = tda1997x_parse_dt(state); |
| if (ret < 0) { |
| v4l_err(client, "DT parsing error\n"); |
| goto err_free_state; |
| } |
| } else if (client->dev.platform_data) { |
| struct tda1997x_platform_data *pdata = |
| client->dev.platform_data; |
| state->info = |
| (const struct tda1997x_chip_info *)id->driver_data; |
| state->pdata = *pdata; |
| } else { |
| v4l_err(client, "No platform data\n"); |
| ret = -ENODEV; |
| goto err_free_state; |
| } |
| |
| ret = tda1997x_get_regulators(state); |
| if (ret) |
| goto err_free_state; |
| |
| ret = tda1997x_set_power(state, 1); |
| if (ret) |
| goto err_free_state; |
| |
| mutex_init(&state->page_lock); |
| mutex_init(&state->lock); |
| state->page = 0xff; |
| |
| INIT_DELAYED_WORK(&state->delayed_work_enable_hpd, |
| tda1997x_delayed_work_enable_hpd); |
| |
| /* set video format based on chip and bus width */ |
| ret = tda1997x_identify_module(state); |
| if (ret) |
| goto err_free_mutex; |
| |
| /* initialize subdev */ |
| sd = &state->sd; |
| v4l2_i2c_subdev_init(sd, client, &tda1997x_subdev_ops); |
| sd->internal_ops = &tda1997x_internal_ops; |
| snprintf(sd->name, sizeof(sd->name), "%s %d-%04x", |
| id->name, i2c_adapter_id(client->adapter), |
| client->addr); |
| sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; |
| sd->entity.function = MEDIA_ENT_F_DV_DECODER; |
| sd->entity.ops = &tda1997x_media_ops; |
| |
| /* set allowed mbus modes based on chip, bus-type, and bus-width */ |
| i = 0; |
| mbus_codes = state->mbus_codes; |
| switch (state->info->type) { |
| case TDA19973: |
| switch (pdata->vidout_bus_type) { |
| case V4L2_MBUS_PARALLEL: |
| switch (pdata->vidout_bus_width) { |
| case 36: |
| mbus_codes[i++] = MEDIA_BUS_FMT_RGB121212_1X36; |
| mbus_codes[i++] = MEDIA_BUS_FMT_YUV12_1X36; |
| fallthrough; |
| case 24: |
| mbus_codes[i++] = MEDIA_BUS_FMT_UYVY12_1X24; |
| break; |
| } |
| break; |
| case V4L2_MBUS_BT656: |
| switch (pdata->vidout_bus_width) { |
| case 36: |
| case 24: |
| case 12: |
| mbus_codes[i++] = MEDIA_BUS_FMT_UYVY12_2X12; |
| mbus_codes[i++] = MEDIA_BUS_FMT_UYVY10_2X10; |
| mbus_codes[i++] = MEDIA_BUS_FMT_UYVY8_2X8; |
| break; |
| } |
| break; |
| default: |
| break; |
| } |
| break; |
| case TDA19971: |
| switch (pdata->vidout_bus_type) { |
| case V4L2_MBUS_PARALLEL: |
| switch (pdata->vidout_bus_width) { |
| case 24: |
| mbus_codes[i++] = MEDIA_BUS_FMT_RGB888_1X24; |
| mbus_codes[i++] = MEDIA_BUS_FMT_YUV8_1X24; |
| mbus_codes[i++] = MEDIA_BUS_FMT_UYVY12_1X24; |
| fallthrough; |
| case 20: |
| mbus_codes[i++] = MEDIA_BUS_FMT_UYVY10_1X20; |
| fallthrough; |
| case 16: |
| mbus_codes[i++] = MEDIA_BUS_FMT_UYVY8_1X16; |
| break; |
| } |
| break; |
| case V4L2_MBUS_BT656: |
| switch (pdata->vidout_bus_width) { |
| case 24: |
| case 20: |
| case 16: |
| case 12: |
| mbus_codes[i++] = MEDIA_BUS_FMT_UYVY12_2X12; |
| fallthrough; |
| case 10: |
| mbus_codes[i++] = MEDIA_BUS_FMT_UYVY10_2X10; |
| fallthrough; |
| case 8: |
| mbus_codes[i++] = MEDIA_BUS_FMT_UYVY8_2X8; |
| break; |
| } |
| break; |
| default: |
| break; |
| } |
| break; |
| } |
| if (WARN_ON(i > ARRAY_SIZE(state->mbus_codes))) { |
| ret = -EINVAL; |
| goto err_free_mutex; |
| } |
| |
| /* default format */ |
| tda1997x_setup_format(state, state->mbus_codes[0]); |
| state->timings = cea1920x1080; |
| |
| /* |
| * default to SRGB full range quantization |
| * (in case we don't get an infoframe such as DVI signal |
| */ |
| state->colorimetry.colorspace = V4L2_COLORSPACE_SRGB; |
| state->colorimetry.quantization = V4L2_QUANTIZATION_FULL_RANGE; |
| |
| /* disable/reset HDCP to get correct I2C access to Rx HDMI */ |
| io_write(sd, REG_MAN_SUS_HDMI_SEL, MAN_RST_HDCP | MAN_DIS_HDCP); |
| |
| /* |
| * if N2 version, reset compdel_bp as it may generate some small pixel |
| * shifts in case of embedded sync/or delay lower than 4 |
| */ |
| if (state->chip_revision != 0) { |
| io_write(sd, REG_MAN_SUS_HDMI_SEL, 0x00); |
| io_write(sd, REG_VDP_CTRL, 0x1f); |
| } |
| |
| v4l_info(client, "NXP %s N%d detected\n", state->info->name, |
| state->chip_revision + 1); |
| v4l_info(client, "video: %dbit %s %d formats available\n", |
| pdata->vidout_bus_width, |
| (pdata->vidout_bus_type == V4L2_MBUS_PARALLEL) ? |
| "parallel" : "BT656", |
| i); |
| if (pdata->audout_format) { |
| v4l_info(client, "audio: %dch %s layout%d sysclk=%d*fs\n", |
| pdata->audout_layout ? 2 : 8, |
| audfmt_names[pdata->audout_format], |
| pdata->audout_layout, |
| pdata->audout_mclk_fs); |
| } |
| |
| ret = 0x34 + ((io_read(sd, REG_SLAVE_ADDR)>>4) & 0x03); |
| state->client_cec = devm_i2c_new_dummy_device(&client->dev, |
| client->adapter, ret); |
| if (IS_ERR(state->client_cec)) { |
| ret = PTR_ERR(state->client_cec); |
| goto err_free_mutex; |
| } |
| |
| v4l_info(client, "CEC slave address 0x%02x\n", ret); |
| |
| ret = tda1997x_core_init(sd); |
| if (ret) |
| goto err_free_mutex; |
| |
| /* control handlers */ |
| hdl = &state->hdl; |
| v4l2_ctrl_handler_init(hdl, 3); |
| ctrl = v4l2_ctrl_new_std_menu(hdl, &tda1997x_ctrl_ops, |
| V4L2_CID_DV_RX_IT_CONTENT_TYPE, |
| V4L2_DV_IT_CONTENT_TYPE_NO_ITC, 0, |
| V4L2_DV_IT_CONTENT_TYPE_NO_ITC); |
| if (ctrl) |
| ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; |
| /* custom controls */ |
| state->detect_tx_5v_ctrl = v4l2_ctrl_new_std(hdl, NULL, |
| V4L2_CID_DV_RX_POWER_PRESENT, 0, 1, 0, 0); |
| state->rgb_quantization_range_ctrl = v4l2_ctrl_new_std_menu(hdl, |
| &tda1997x_ctrl_ops, |
| V4L2_CID_DV_RX_RGB_RANGE, V4L2_DV_RGB_RANGE_FULL, 0, |
| V4L2_DV_RGB_RANGE_AUTO); |
| state->sd.ctrl_handler = hdl; |
| if (hdl->error) { |
| ret = hdl->error; |
| goto err_free_handler; |
| } |
| v4l2_ctrl_handler_setup(hdl); |
| |
| /* initialize source pads */ |
| state->pads[TDA1997X_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; |
| ret = media_entity_pads_init(&sd->entity, TDA1997X_NUM_PADS, |
| state->pads); |
| if (ret) { |
| v4l_err(client, "failed entity_init: %d", ret); |
| goto err_free_handler; |
| } |
| |
| ret = v4l2_async_register_subdev(sd); |
| if (ret) |
| goto err_free_media; |
| |
| /* register audio DAI */ |
| if (pdata->audout_format) { |
| u64 formats; |
| |
| if (pdata->audout_width == 32) |
| formats = SNDRV_PCM_FMTBIT_S32_LE; |
| else |
| formats = SNDRV_PCM_FMTBIT_S16_LE; |
| tda1997x_audio_dai.capture.formats = formats; |
| ret = devm_snd_soc_register_component(&state->client->dev, |
| &tda1997x_codec_driver, |
| &tda1997x_audio_dai, 1); |
| if (ret) { |
| dev_err(&client->dev, "register audio codec failed\n"); |
| goto err_free_media; |
| } |
| v4l_info(state->client, "registered audio codec\n"); |
| } |
| |
| /* request irq */ |
| ret = devm_request_threaded_irq(&client->dev, client->irq, |
| NULL, tda1997x_isr_thread, |
| IRQF_TRIGGER_LOW | IRQF_ONESHOT, |
| KBUILD_MODNAME, state); |
| if (ret) { |
| v4l_err(client, "irq%d reg failed: %d\n", client->irq, ret); |
| goto err_free_media; |
| } |
| |
| return 0; |
| |
| err_free_media: |
| media_entity_cleanup(&sd->entity); |
| err_free_handler: |
| v4l2_ctrl_handler_free(&state->hdl); |
| err_free_mutex: |
| cancel_delayed_work(&state->delayed_work_enable_hpd); |
| mutex_destroy(&state->page_lock); |
| mutex_destroy(&state->lock); |
| tda1997x_set_power(state, 0); |
| err_free_state: |
| kfree(state); |
| dev_err(&client->dev, "%s failed: %d\n", __func__, ret); |
| |
| return ret; |
| } |
| |
| static void tda1997x_remove(struct i2c_client *client) |
| { |
| struct v4l2_subdev *sd = i2c_get_clientdata(client); |
| struct tda1997x_state *state = to_state(sd); |
| struct tda1997x_platform_data *pdata = &state->pdata; |
| |
| if (pdata->audout_format) { |
| mutex_destroy(&state->audio_lock); |
| } |
| |
| disable_irq(state->client->irq); |
| tda1997x_power_mode(state, 0); |
| |
| v4l2_async_unregister_subdev(sd); |
| media_entity_cleanup(&sd->entity); |
| v4l2_ctrl_handler_free(&state->hdl); |
| regulator_bulk_disable(TDA1997X_NUM_SUPPLIES, state->supplies); |
| cancel_delayed_work_sync(&state->delayed_work_enable_hpd); |
| mutex_destroy(&state->page_lock); |
| mutex_destroy(&state->lock); |
| |
| kfree(state); |
| } |
| |
| static struct i2c_driver tda1997x_i2c_driver = { |
| .driver = { |
| .name = "tda1997x", |
| .of_match_table = of_match_ptr(tda1997x_of_id), |
| }, |
| .probe = tda1997x_probe, |
| .remove = tda1997x_remove, |
| .id_table = tda1997x_i2c_id, |
| }; |
| |
| module_i2c_driver(tda1997x_i2c_driver); |
| |
| MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>"); |
| MODULE_DESCRIPTION("TDA1997X HDMI Receiver driver"); |
| MODULE_LICENSE("GPL v2"); |