162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * vivid-vbi-gen.c - vbi generator support functions. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/errno.h> 962306a36Sopenharmony_ci#include <linux/kernel.h> 1062306a36Sopenharmony_ci#include <linux/ktime.h> 1162306a36Sopenharmony_ci#include <linux/string.h> 1262306a36Sopenharmony_ci#include <linux/videodev2.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include "vivid-vbi-gen.h" 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_cistatic void wss_insert(u8 *wss, u32 val, unsigned size) 1762306a36Sopenharmony_ci{ 1862306a36Sopenharmony_ci while (size--) 1962306a36Sopenharmony_ci *wss++ = (val & (1 << size)) ? 0xc0 : 0x10; 2062306a36Sopenharmony_ci} 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_cistatic void vivid_vbi_gen_wss_raw(const struct v4l2_sliced_vbi_data *data, 2362306a36Sopenharmony_ci u8 *buf, unsigned sampling_rate) 2462306a36Sopenharmony_ci{ 2562306a36Sopenharmony_ci const unsigned rate = 5000000; /* WSS has a 5 MHz transmission rate */ 2662306a36Sopenharmony_ci u8 wss[29 + 24 + 24 + 24 + 18 + 18] = { 0 }; 2762306a36Sopenharmony_ci const unsigned zero = 0x07; 2862306a36Sopenharmony_ci const unsigned one = 0x38; 2962306a36Sopenharmony_ci unsigned bit = 0; 3062306a36Sopenharmony_ci u16 wss_data; 3162306a36Sopenharmony_ci int i; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci wss_insert(wss + bit, 0x1f1c71c7, 29); bit += 29; 3462306a36Sopenharmony_ci wss_insert(wss + bit, 0x1e3c1f, 24); bit += 24; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci wss_data = (data->data[1] << 8) | data->data[0]; 3762306a36Sopenharmony_ci for (i = 0; i <= 13; i++, bit += 6) 3862306a36Sopenharmony_ci wss_insert(wss + bit, (wss_data & (1 << i)) ? one : zero, 6); 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci for (i = 0, bit = 0; bit < sizeof(wss); bit++) { 4162306a36Sopenharmony_ci unsigned n = ((bit + 1) * sampling_rate) / rate; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci while (i < n) 4462306a36Sopenharmony_ci buf[i++] = wss[bit]; 4562306a36Sopenharmony_ci } 4662306a36Sopenharmony_ci} 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic void vivid_vbi_gen_teletext_raw(const struct v4l2_sliced_vbi_data *data, 4962306a36Sopenharmony_ci u8 *buf, unsigned sampling_rate) 5062306a36Sopenharmony_ci{ 5162306a36Sopenharmony_ci const unsigned rate = 6937500 / 10; /* Teletext has a 6.9375 MHz transmission rate */ 5262306a36Sopenharmony_ci u8 teletext[45] = { 0x55, 0x55, 0x27 }; 5362306a36Sopenharmony_ci unsigned bit = 0; 5462306a36Sopenharmony_ci int i; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci memcpy(teletext + 3, data->data, sizeof(teletext) - 3); 5762306a36Sopenharmony_ci /* prevents 32 bit overflow */ 5862306a36Sopenharmony_ci sampling_rate /= 10; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci for (i = 0, bit = 0; bit < sizeof(teletext) * 8; bit++) { 6162306a36Sopenharmony_ci unsigned n = ((bit + 1) * sampling_rate) / rate; 6262306a36Sopenharmony_ci u8 val = (teletext[bit / 8] & (1 << (bit & 7))) ? 0xc0 : 0x10; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci while (i < n) 6562306a36Sopenharmony_ci buf[i++] = val; 6662306a36Sopenharmony_ci } 6762306a36Sopenharmony_ci} 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cistatic void cc_insert(u8 *cc, u8 ch) 7062306a36Sopenharmony_ci{ 7162306a36Sopenharmony_ci unsigned tot = 0; 7262306a36Sopenharmony_ci unsigned i; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci for (i = 0; i < 7; i++) { 7562306a36Sopenharmony_ci cc[2 * i] = cc[2 * i + 1] = (ch & (1 << i)) ? 1 : 0; 7662306a36Sopenharmony_ci tot += cc[2 * i]; 7762306a36Sopenharmony_ci } 7862306a36Sopenharmony_ci cc[14] = cc[15] = !(tot & 1); 7962306a36Sopenharmony_ci} 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci#define CC_PREAMBLE_BITS (14 + 4 + 2) 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_cistatic void vivid_vbi_gen_cc_raw(const struct v4l2_sliced_vbi_data *data, 8462306a36Sopenharmony_ci u8 *buf, unsigned sampling_rate) 8562306a36Sopenharmony_ci{ 8662306a36Sopenharmony_ci const unsigned rate = 1000000; /* CC has a 1 MHz transmission rate */ 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci u8 cc[CC_PREAMBLE_BITS + 2 * 16] = { 8962306a36Sopenharmony_ci /* Clock run-in: 7 cycles */ 9062306a36Sopenharmony_ci 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 9162306a36Sopenharmony_ci /* 2 cycles of 0 */ 9262306a36Sopenharmony_ci 0, 0, 0, 0, 9362306a36Sopenharmony_ci /* Start bit of 1 (each bit is two cycles) */ 9462306a36Sopenharmony_ci 1, 1 9562306a36Sopenharmony_ci }; 9662306a36Sopenharmony_ci unsigned bit, i; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci cc_insert(cc + CC_PREAMBLE_BITS, data->data[0]); 9962306a36Sopenharmony_ci cc_insert(cc + CC_PREAMBLE_BITS + 16, data->data[1]); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci for (i = 0, bit = 0; bit < sizeof(cc); bit++) { 10262306a36Sopenharmony_ci unsigned n = ((bit + 1) * sampling_rate) / rate; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci while (i < n) 10562306a36Sopenharmony_ci buf[i++] = cc[bit] ? 0xc0 : 0x10; 10662306a36Sopenharmony_ci } 10762306a36Sopenharmony_ci} 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_civoid vivid_vbi_gen_raw(const struct vivid_vbi_gen_data *vbi, 11062306a36Sopenharmony_ci const struct v4l2_vbi_format *vbi_fmt, u8 *buf) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci unsigned idx; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci for (idx = 0; idx < 25; idx++) { 11562306a36Sopenharmony_ci const struct v4l2_sliced_vbi_data *data = vbi->data + idx; 11662306a36Sopenharmony_ci unsigned start_2nd_field; 11762306a36Sopenharmony_ci unsigned line = data->line; 11862306a36Sopenharmony_ci u8 *linebuf = buf; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci start_2nd_field = (data->id & V4L2_SLICED_VBI_525) ? 263 : 313; 12162306a36Sopenharmony_ci if (data->field) 12262306a36Sopenharmony_ci line += start_2nd_field; 12362306a36Sopenharmony_ci line -= vbi_fmt->start[data->field]; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci if (vbi_fmt->flags & V4L2_VBI_INTERLACED) 12662306a36Sopenharmony_ci linebuf += (line * 2 + data->field) * 12762306a36Sopenharmony_ci vbi_fmt->samples_per_line; 12862306a36Sopenharmony_ci else 12962306a36Sopenharmony_ci linebuf += (line + data->field * vbi_fmt->count[0]) * 13062306a36Sopenharmony_ci vbi_fmt->samples_per_line; 13162306a36Sopenharmony_ci if (data->id == V4L2_SLICED_CAPTION_525) 13262306a36Sopenharmony_ci vivid_vbi_gen_cc_raw(data, linebuf, vbi_fmt->sampling_rate); 13362306a36Sopenharmony_ci else if (data->id == V4L2_SLICED_WSS_625) 13462306a36Sopenharmony_ci vivid_vbi_gen_wss_raw(data, linebuf, vbi_fmt->sampling_rate); 13562306a36Sopenharmony_ci else if (data->id == V4L2_SLICED_TELETEXT_B) 13662306a36Sopenharmony_ci vivid_vbi_gen_teletext_raw(data, linebuf, vbi_fmt->sampling_rate); 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic const u8 vivid_cc_sequence1[30] = { 14162306a36Sopenharmony_ci 0x14, 0x20, /* Resume Caption Loading */ 14262306a36Sopenharmony_ci 'H', 'e', 14362306a36Sopenharmony_ci 'l', 'l', 14462306a36Sopenharmony_ci 'o', ' ', 14562306a36Sopenharmony_ci 'w', 'o', 14662306a36Sopenharmony_ci 'r', 'l', 14762306a36Sopenharmony_ci 'd', '!', 14862306a36Sopenharmony_ci 0x14, 0x2f, /* End of Caption */ 14962306a36Sopenharmony_ci}; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_cistatic const u8 vivid_cc_sequence2[30] = { 15262306a36Sopenharmony_ci 0x14, 0x20, /* Resume Caption Loading */ 15362306a36Sopenharmony_ci 'C', 'l', 15462306a36Sopenharmony_ci 'o', 's', 15562306a36Sopenharmony_ci 'e', 'd', 15662306a36Sopenharmony_ci ' ', 'c', 15762306a36Sopenharmony_ci 'a', 'p', 15862306a36Sopenharmony_ci 't', 'i', 15962306a36Sopenharmony_ci 'o', 'n', 16062306a36Sopenharmony_ci 's', ' ', 16162306a36Sopenharmony_ci 't', 'e', 16262306a36Sopenharmony_ci 's', 't', 16362306a36Sopenharmony_ci 0x14, 0x2f, /* End of Caption */ 16462306a36Sopenharmony_ci}; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cistatic u8 calc_parity(u8 val) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci unsigned i; 16962306a36Sopenharmony_ci unsigned tot = 0; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci for (i = 0; i < 7; i++) 17262306a36Sopenharmony_ci tot += (val & (1 << i)) ? 1 : 0; 17362306a36Sopenharmony_ci return val | ((tot & 1) ? 0 : 0x80); 17462306a36Sopenharmony_ci} 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_cistatic void vivid_vbi_gen_set_time_of_day(u8 *packet) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci struct tm tm; 17962306a36Sopenharmony_ci u8 checksum, i; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci time64_to_tm(ktime_get_real_seconds(), 0, &tm); 18262306a36Sopenharmony_ci packet[0] = calc_parity(0x07); 18362306a36Sopenharmony_ci packet[1] = calc_parity(0x01); 18462306a36Sopenharmony_ci packet[2] = calc_parity(0x40 | tm.tm_min); 18562306a36Sopenharmony_ci packet[3] = calc_parity(0x40 | tm.tm_hour); 18662306a36Sopenharmony_ci packet[4] = calc_parity(0x40 | tm.tm_mday); 18762306a36Sopenharmony_ci if (tm.tm_mday == 1 && tm.tm_mon == 2 && 18862306a36Sopenharmony_ci sys_tz.tz_minuteswest > tm.tm_min + tm.tm_hour * 60) 18962306a36Sopenharmony_ci packet[4] = calc_parity(0x60 | tm.tm_mday); 19062306a36Sopenharmony_ci packet[5] = calc_parity(0x40 | (1 + tm.tm_mon)); 19162306a36Sopenharmony_ci packet[6] = calc_parity(0x40 | (1 + tm.tm_wday)); 19262306a36Sopenharmony_ci packet[7] = calc_parity(0x40 | ((tm.tm_year - 90) & 0x3f)); 19362306a36Sopenharmony_ci packet[8] = calc_parity(0x0f); 19462306a36Sopenharmony_ci for (checksum = i = 0; i <= 8; i++) 19562306a36Sopenharmony_ci checksum += packet[i] & 0x7f; 19662306a36Sopenharmony_ci packet[9] = calc_parity(0x100 - checksum); 19762306a36Sopenharmony_ci packet[10] = calc_parity(0x07); 19862306a36Sopenharmony_ci packet[11] = calc_parity(0x04); 19962306a36Sopenharmony_ci if (sys_tz.tz_minuteswest >= 0) 20062306a36Sopenharmony_ci packet[12] = calc_parity(0x40 | ((sys_tz.tz_minuteswest / 60) & 0x1f)); 20162306a36Sopenharmony_ci else 20262306a36Sopenharmony_ci packet[12] = calc_parity(0x40 | ((24 + sys_tz.tz_minuteswest / 60) & 0x1f)); 20362306a36Sopenharmony_ci packet[13] = calc_parity(0); 20462306a36Sopenharmony_ci packet[14] = calc_parity(0x0f); 20562306a36Sopenharmony_ci for (checksum = 0, i = 10; i <= 14; i++) 20662306a36Sopenharmony_ci checksum += packet[i] & 0x7f; 20762306a36Sopenharmony_ci packet[15] = calc_parity(0x100 - checksum); 20862306a36Sopenharmony_ci} 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_cistatic const u8 hamming[16] = { 21162306a36Sopenharmony_ci 0x15, 0x02, 0x49, 0x5e, 0x64, 0x73, 0x38, 0x2f, 21262306a36Sopenharmony_ci 0xd0, 0xc7, 0x8c, 0x9b, 0xa1, 0xb6, 0xfd, 0xea 21362306a36Sopenharmony_ci}; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_cistatic void vivid_vbi_gen_teletext(u8 *packet, unsigned line, unsigned frame) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci unsigned offset = 2; 21862306a36Sopenharmony_ci unsigned i; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci packet[0] = hamming[1 + ((line & 1) << 3)]; 22162306a36Sopenharmony_ci packet[1] = hamming[line >> 1]; 22262306a36Sopenharmony_ci memset(packet + 2, 0x20, 40); 22362306a36Sopenharmony_ci if (line == 0) { 22462306a36Sopenharmony_ci /* subcode */ 22562306a36Sopenharmony_ci packet[2] = hamming[frame % 10]; 22662306a36Sopenharmony_ci packet[3] = hamming[frame / 10]; 22762306a36Sopenharmony_ci packet[4] = hamming[0]; 22862306a36Sopenharmony_ci packet[5] = hamming[0]; 22962306a36Sopenharmony_ci packet[6] = hamming[0]; 23062306a36Sopenharmony_ci packet[7] = hamming[0]; 23162306a36Sopenharmony_ci packet[8] = hamming[0]; 23262306a36Sopenharmony_ci packet[9] = hamming[1]; 23362306a36Sopenharmony_ci offset = 10; 23462306a36Sopenharmony_ci } 23562306a36Sopenharmony_ci packet += offset; 23662306a36Sopenharmony_ci memcpy(packet, "Page: 100 Row: 10", 17); 23762306a36Sopenharmony_ci packet[7] = '0' + frame / 10; 23862306a36Sopenharmony_ci packet[8] = '0' + frame % 10; 23962306a36Sopenharmony_ci packet[15] = '0' + line / 10; 24062306a36Sopenharmony_ci packet[16] = '0' + line % 10; 24162306a36Sopenharmony_ci for (i = 0; i < 42 - offset; i++) 24262306a36Sopenharmony_ci packet[i] = calc_parity(packet[i]); 24362306a36Sopenharmony_ci} 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_civoid vivid_vbi_gen_sliced(struct vivid_vbi_gen_data *vbi, 24662306a36Sopenharmony_ci bool is_60hz, unsigned seqnr) 24762306a36Sopenharmony_ci{ 24862306a36Sopenharmony_ci struct v4l2_sliced_vbi_data *data0 = vbi->data; 24962306a36Sopenharmony_ci struct v4l2_sliced_vbi_data *data1 = vbi->data + 1; 25062306a36Sopenharmony_ci unsigned frame = seqnr % 60; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci memset(vbi->data, 0, sizeof(vbi->data)); 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci if (!is_60hz) { 25562306a36Sopenharmony_ci unsigned i; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci for (i = 0; i <= 11; i++) { 25862306a36Sopenharmony_ci data0->id = V4L2_SLICED_TELETEXT_B; 25962306a36Sopenharmony_ci data0->line = 7 + i; 26062306a36Sopenharmony_ci vivid_vbi_gen_teletext(data0->data, i, frame); 26162306a36Sopenharmony_ci data0++; 26262306a36Sopenharmony_ci } 26362306a36Sopenharmony_ci data0->id = V4L2_SLICED_WSS_625; 26462306a36Sopenharmony_ci data0->line = 23; 26562306a36Sopenharmony_ci /* 4x3 video aspect ratio */ 26662306a36Sopenharmony_ci data0->data[0] = 0x08; 26762306a36Sopenharmony_ci data0++; 26862306a36Sopenharmony_ci for (i = 0; i <= 11; i++) { 26962306a36Sopenharmony_ci data0->id = V4L2_SLICED_TELETEXT_B; 27062306a36Sopenharmony_ci data0->field = 1; 27162306a36Sopenharmony_ci data0->line = 7 + i; 27262306a36Sopenharmony_ci vivid_vbi_gen_teletext(data0->data, 12 + i, frame); 27362306a36Sopenharmony_ci data0++; 27462306a36Sopenharmony_ci } 27562306a36Sopenharmony_ci return; 27662306a36Sopenharmony_ci } 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci data0->id = V4L2_SLICED_CAPTION_525; 27962306a36Sopenharmony_ci data0->line = 21; 28062306a36Sopenharmony_ci data1->id = V4L2_SLICED_CAPTION_525; 28162306a36Sopenharmony_ci data1->field = 1; 28262306a36Sopenharmony_ci data1->line = 21; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci if (frame < 15) { 28562306a36Sopenharmony_ci data0->data[0] = calc_parity(vivid_cc_sequence1[2 * frame]); 28662306a36Sopenharmony_ci data0->data[1] = calc_parity(vivid_cc_sequence1[2 * frame + 1]); 28762306a36Sopenharmony_ci } else if (frame >= 30 && frame < 45) { 28862306a36Sopenharmony_ci frame -= 30; 28962306a36Sopenharmony_ci data0->data[0] = calc_parity(vivid_cc_sequence2[2 * frame]); 29062306a36Sopenharmony_ci data0->data[1] = calc_parity(vivid_cc_sequence2[2 * frame + 1]); 29162306a36Sopenharmony_ci } else { 29262306a36Sopenharmony_ci data0->data[0] = calc_parity(0); 29362306a36Sopenharmony_ci data0->data[1] = calc_parity(0); 29462306a36Sopenharmony_ci } 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci frame = seqnr % (30 * 60); 29762306a36Sopenharmony_ci switch (frame) { 29862306a36Sopenharmony_ci case 0: 29962306a36Sopenharmony_ci vivid_vbi_gen_set_time_of_day(vbi->time_of_day_packet); 30062306a36Sopenharmony_ci fallthrough; 30162306a36Sopenharmony_ci case 1 ... 7: 30262306a36Sopenharmony_ci data1->data[0] = vbi->time_of_day_packet[frame * 2]; 30362306a36Sopenharmony_ci data1->data[1] = vbi->time_of_day_packet[frame * 2 + 1]; 30462306a36Sopenharmony_ci break; 30562306a36Sopenharmony_ci default: 30662306a36Sopenharmony_ci data1->data[0] = calc_parity(0); 30762306a36Sopenharmony_ci data1->data[1] = calc_parity(0); 30862306a36Sopenharmony_ci break; 30962306a36Sopenharmony_ci } 31062306a36Sopenharmony_ci} 311