18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * vivid-vbi-gen.c - vbi generator support functions. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/errno.h> 98c2ecf20Sopenharmony_ci#include <linux/kernel.h> 108c2ecf20Sopenharmony_ci#include <linux/ktime.h> 118c2ecf20Sopenharmony_ci#include <linux/string.h> 128c2ecf20Sopenharmony_ci#include <linux/videodev2.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include "vivid-vbi-gen.h" 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_cistatic void wss_insert(u8 *wss, u32 val, unsigned size) 178c2ecf20Sopenharmony_ci{ 188c2ecf20Sopenharmony_ci while (size--) 198c2ecf20Sopenharmony_ci *wss++ = (val & (1 << size)) ? 0xc0 : 0x10; 208c2ecf20Sopenharmony_ci} 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_cistatic void vivid_vbi_gen_wss_raw(const struct v4l2_sliced_vbi_data *data, 238c2ecf20Sopenharmony_ci u8 *buf, unsigned sampling_rate) 248c2ecf20Sopenharmony_ci{ 258c2ecf20Sopenharmony_ci const unsigned rate = 5000000; /* WSS has a 5 MHz transmission rate */ 268c2ecf20Sopenharmony_ci u8 wss[29 + 24 + 24 + 24 + 18 + 18] = { 0 }; 278c2ecf20Sopenharmony_ci const unsigned zero = 0x07; 288c2ecf20Sopenharmony_ci const unsigned one = 0x38; 298c2ecf20Sopenharmony_ci unsigned bit = 0; 308c2ecf20Sopenharmony_ci u16 wss_data; 318c2ecf20Sopenharmony_ci int i; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci wss_insert(wss + bit, 0x1f1c71c7, 29); bit += 29; 348c2ecf20Sopenharmony_ci wss_insert(wss + bit, 0x1e3c1f, 24); bit += 24; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci wss_data = (data->data[1] << 8) | data->data[0]; 378c2ecf20Sopenharmony_ci for (i = 0; i <= 13; i++, bit += 6) 388c2ecf20Sopenharmony_ci wss_insert(wss + bit, (wss_data & (1 << i)) ? one : zero, 6); 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci for (i = 0, bit = 0; bit < sizeof(wss); bit++) { 418c2ecf20Sopenharmony_ci unsigned n = ((bit + 1) * sampling_rate) / rate; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci while (i < n) 448c2ecf20Sopenharmony_ci buf[i++] = wss[bit]; 458c2ecf20Sopenharmony_ci } 468c2ecf20Sopenharmony_ci} 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistatic void vivid_vbi_gen_teletext_raw(const struct v4l2_sliced_vbi_data *data, 498c2ecf20Sopenharmony_ci u8 *buf, unsigned sampling_rate) 508c2ecf20Sopenharmony_ci{ 518c2ecf20Sopenharmony_ci const unsigned rate = 6937500 / 10; /* Teletext has a 6.9375 MHz transmission rate */ 528c2ecf20Sopenharmony_ci u8 teletext[45] = { 0x55, 0x55, 0x27 }; 538c2ecf20Sopenharmony_ci unsigned bit = 0; 548c2ecf20Sopenharmony_ci int i; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci memcpy(teletext + 3, data->data, sizeof(teletext) - 3); 578c2ecf20Sopenharmony_ci /* prevents 32 bit overflow */ 588c2ecf20Sopenharmony_ci sampling_rate /= 10; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci for (i = 0, bit = 0; bit < sizeof(teletext) * 8; bit++) { 618c2ecf20Sopenharmony_ci unsigned n = ((bit + 1) * sampling_rate) / rate; 628c2ecf20Sopenharmony_ci u8 val = (teletext[bit / 8] & (1 << (bit & 7))) ? 0xc0 : 0x10; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci while (i < n) 658c2ecf20Sopenharmony_ci buf[i++] = val; 668c2ecf20Sopenharmony_ci } 678c2ecf20Sopenharmony_ci} 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic void cc_insert(u8 *cc, u8 ch) 708c2ecf20Sopenharmony_ci{ 718c2ecf20Sopenharmony_ci unsigned tot = 0; 728c2ecf20Sopenharmony_ci unsigned i; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci for (i = 0; i < 7; i++) { 758c2ecf20Sopenharmony_ci cc[2 * i] = cc[2 * i + 1] = (ch & (1 << i)) ? 1 : 0; 768c2ecf20Sopenharmony_ci tot += cc[2 * i]; 778c2ecf20Sopenharmony_ci } 788c2ecf20Sopenharmony_ci cc[14] = cc[15] = !(tot & 1); 798c2ecf20Sopenharmony_ci} 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci#define CC_PREAMBLE_BITS (14 + 4 + 2) 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_cistatic void vivid_vbi_gen_cc_raw(const struct v4l2_sliced_vbi_data *data, 848c2ecf20Sopenharmony_ci u8 *buf, unsigned sampling_rate) 858c2ecf20Sopenharmony_ci{ 868c2ecf20Sopenharmony_ci const unsigned rate = 1000000; /* CC has a 1 MHz transmission rate */ 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci u8 cc[CC_PREAMBLE_BITS + 2 * 16] = { 898c2ecf20Sopenharmony_ci /* Clock run-in: 7 cycles */ 908c2ecf20Sopenharmony_ci 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 918c2ecf20Sopenharmony_ci /* 2 cycles of 0 */ 928c2ecf20Sopenharmony_ci 0, 0, 0, 0, 938c2ecf20Sopenharmony_ci /* Start bit of 1 (each bit is two cycles) */ 948c2ecf20Sopenharmony_ci 1, 1 958c2ecf20Sopenharmony_ci }; 968c2ecf20Sopenharmony_ci unsigned bit, i; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci cc_insert(cc + CC_PREAMBLE_BITS, data->data[0]); 998c2ecf20Sopenharmony_ci cc_insert(cc + CC_PREAMBLE_BITS + 16, data->data[1]); 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci for (i = 0, bit = 0; bit < sizeof(cc); bit++) { 1028c2ecf20Sopenharmony_ci unsigned n = ((bit + 1) * sampling_rate) / rate; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci while (i < n) 1058c2ecf20Sopenharmony_ci buf[i++] = cc[bit] ? 0xc0 : 0x10; 1068c2ecf20Sopenharmony_ci } 1078c2ecf20Sopenharmony_ci} 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_civoid vivid_vbi_gen_raw(const struct vivid_vbi_gen_data *vbi, 1108c2ecf20Sopenharmony_ci const struct v4l2_vbi_format *vbi_fmt, u8 *buf) 1118c2ecf20Sopenharmony_ci{ 1128c2ecf20Sopenharmony_ci unsigned idx; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci for (idx = 0; idx < 25; idx++) { 1158c2ecf20Sopenharmony_ci const struct v4l2_sliced_vbi_data *data = vbi->data + idx; 1168c2ecf20Sopenharmony_ci unsigned start_2nd_field; 1178c2ecf20Sopenharmony_ci unsigned line = data->line; 1188c2ecf20Sopenharmony_ci u8 *linebuf = buf; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci start_2nd_field = (data->id & V4L2_SLICED_VBI_525) ? 263 : 313; 1218c2ecf20Sopenharmony_ci if (data->field) 1228c2ecf20Sopenharmony_ci line += start_2nd_field; 1238c2ecf20Sopenharmony_ci line -= vbi_fmt->start[data->field]; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci if (vbi_fmt->flags & V4L2_VBI_INTERLACED) 1268c2ecf20Sopenharmony_ci linebuf += (line * 2 + data->field) * 1278c2ecf20Sopenharmony_ci vbi_fmt->samples_per_line; 1288c2ecf20Sopenharmony_ci else 1298c2ecf20Sopenharmony_ci linebuf += (line + data->field * vbi_fmt->count[0]) * 1308c2ecf20Sopenharmony_ci vbi_fmt->samples_per_line; 1318c2ecf20Sopenharmony_ci if (data->id == V4L2_SLICED_CAPTION_525) 1328c2ecf20Sopenharmony_ci vivid_vbi_gen_cc_raw(data, linebuf, vbi_fmt->sampling_rate); 1338c2ecf20Sopenharmony_ci else if (data->id == V4L2_SLICED_WSS_625) 1348c2ecf20Sopenharmony_ci vivid_vbi_gen_wss_raw(data, linebuf, vbi_fmt->sampling_rate); 1358c2ecf20Sopenharmony_ci else if (data->id == V4L2_SLICED_TELETEXT_B) 1368c2ecf20Sopenharmony_ci vivid_vbi_gen_teletext_raw(data, linebuf, vbi_fmt->sampling_rate); 1378c2ecf20Sopenharmony_ci } 1388c2ecf20Sopenharmony_ci} 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_cistatic const u8 vivid_cc_sequence1[30] = { 1418c2ecf20Sopenharmony_ci 0x14, 0x20, /* Resume Caption Loading */ 1428c2ecf20Sopenharmony_ci 'H', 'e', 1438c2ecf20Sopenharmony_ci 'l', 'l', 1448c2ecf20Sopenharmony_ci 'o', ' ', 1458c2ecf20Sopenharmony_ci 'w', 'o', 1468c2ecf20Sopenharmony_ci 'r', 'l', 1478c2ecf20Sopenharmony_ci 'd', '!', 1488c2ecf20Sopenharmony_ci 0x14, 0x2f, /* End of Caption */ 1498c2ecf20Sopenharmony_ci}; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_cistatic const u8 vivid_cc_sequence2[30] = { 1528c2ecf20Sopenharmony_ci 0x14, 0x20, /* Resume Caption Loading */ 1538c2ecf20Sopenharmony_ci 'C', 'l', 1548c2ecf20Sopenharmony_ci 'o', 's', 1558c2ecf20Sopenharmony_ci 'e', 'd', 1568c2ecf20Sopenharmony_ci ' ', 'c', 1578c2ecf20Sopenharmony_ci 'a', 'p', 1588c2ecf20Sopenharmony_ci 't', 'i', 1598c2ecf20Sopenharmony_ci 'o', 'n', 1608c2ecf20Sopenharmony_ci 's', ' ', 1618c2ecf20Sopenharmony_ci 't', 'e', 1628c2ecf20Sopenharmony_ci 's', 't', 1638c2ecf20Sopenharmony_ci 0x14, 0x2f, /* End of Caption */ 1648c2ecf20Sopenharmony_ci}; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_cistatic u8 calc_parity(u8 val) 1678c2ecf20Sopenharmony_ci{ 1688c2ecf20Sopenharmony_ci unsigned i; 1698c2ecf20Sopenharmony_ci unsigned tot = 0; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci for (i = 0; i < 7; i++) 1728c2ecf20Sopenharmony_ci tot += (val & (1 << i)) ? 1 : 0; 1738c2ecf20Sopenharmony_ci return val | ((tot & 1) ? 0 : 0x80); 1748c2ecf20Sopenharmony_ci} 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_cistatic void vivid_vbi_gen_set_time_of_day(u8 *packet) 1778c2ecf20Sopenharmony_ci{ 1788c2ecf20Sopenharmony_ci struct tm tm; 1798c2ecf20Sopenharmony_ci u8 checksum, i; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci time64_to_tm(ktime_get_real_seconds(), 0, &tm); 1828c2ecf20Sopenharmony_ci packet[0] = calc_parity(0x07); 1838c2ecf20Sopenharmony_ci packet[1] = calc_parity(0x01); 1848c2ecf20Sopenharmony_ci packet[2] = calc_parity(0x40 | tm.tm_min); 1858c2ecf20Sopenharmony_ci packet[3] = calc_parity(0x40 | tm.tm_hour); 1868c2ecf20Sopenharmony_ci packet[4] = calc_parity(0x40 | tm.tm_mday); 1878c2ecf20Sopenharmony_ci if (tm.tm_mday == 1 && tm.tm_mon == 2 && 1888c2ecf20Sopenharmony_ci sys_tz.tz_minuteswest > tm.tm_min + tm.tm_hour * 60) 1898c2ecf20Sopenharmony_ci packet[4] = calc_parity(0x60 | tm.tm_mday); 1908c2ecf20Sopenharmony_ci packet[5] = calc_parity(0x40 | (1 + tm.tm_mon)); 1918c2ecf20Sopenharmony_ci packet[6] = calc_parity(0x40 | (1 + tm.tm_wday)); 1928c2ecf20Sopenharmony_ci packet[7] = calc_parity(0x40 | ((tm.tm_year - 90) & 0x3f)); 1938c2ecf20Sopenharmony_ci packet[8] = calc_parity(0x0f); 1948c2ecf20Sopenharmony_ci for (checksum = i = 0; i <= 8; i++) 1958c2ecf20Sopenharmony_ci checksum += packet[i] & 0x7f; 1968c2ecf20Sopenharmony_ci packet[9] = calc_parity(0x100 - checksum); 1978c2ecf20Sopenharmony_ci checksum = 0; 1988c2ecf20Sopenharmony_ci packet[10] = calc_parity(0x07); 1998c2ecf20Sopenharmony_ci packet[11] = calc_parity(0x04); 2008c2ecf20Sopenharmony_ci if (sys_tz.tz_minuteswest >= 0) 2018c2ecf20Sopenharmony_ci packet[12] = calc_parity(0x40 | ((sys_tz.tz_minuteswest / 60) & 0x1f)); 2028c2ecf20Sopenharmony_ci else 2038c2ecf20Sopenharmony_ci packet[12] = calc_parity(0x40 | ((24 + sys_tz.tz_minuteswest / 60) & 0x1f)); 2048c2ecf20Sopenharmony_ci packet[13] = calc_parity(0); 2058c2ecf20Sopenharmony_ci packet[14] = calc_parity(0x0f); 2068c2ecf20Sopenharmony_ci for (checksum = 0, i = 10; i <= 14; i++) 2078c2ecf20Sopenharmony_ci checksum += packet[i] & 0x7f; 2088c2ecf20Sopenharmony_ci packet[15] = calc_parity(0x100 - checksum); 2098c2ecf20Sopenharmony_ci} 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_cistatic const u8 hamming[16] = { 2128c2ecf20Sopenharmony_ci 0x15, 0x02, 0x49, 0x5e, 0x64, 0x73, 0x38, 0x2f, 2138c2ecf20Sopenharmony_ci 0xd0, 0xc7, 0x8c, 0x9b, 0xa1, 0xb6, 0xfd, 0xea 2148c2ecf20Sopenharmony_ci}; 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_cistatic void vivid_vbi_gen_teletext(u8 *packet, unsigned line, unsigned frame) 2178c2ecf20Sopenharmony_ci{ 2188c2ecf20Sopenharmony_ci unsigned offset = 2; 2198c2ecf20Sopenharmony_ci unsigned i; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci packet[0] = hamming[1 + ((line & 1) << 3)]; 2228c2ecf20Sopenharmony_ci packet[1] = hamming[line >> 1]; 2238c2ecf20Sopenharmony_ci memset(packet + 2, 0x20, 40); 2248c2ecf20Sopenharmony_ci if (line == 0) { 2258c2ecf20Sopenharmony_ci /* subcode */ 2268c2ecf20Sopenharmony_ci packet[2] = hamming[frame % 10]; 2278c2ecf20Sopenharmony_ci packet[3] = hamming[frame / 10]; 2288c2ecf20Sopenharmony_ci packet[4] = hamming[0]; 2298c2ecf20Sopenharmony_ci packet[5] = hamming[0]; 2308c2ecf20Sopenharmony_ci packet[6] = hamming[0]; 2318c2ecf20Sopenharmony_ci packet[7] = hamming[0]; 2328c2ecf20Sopenharmony_ci packet[8] = hamming[0]; 2338c2ecf20Sopenharmony_ci packet[9] = hamming[1]; 2348c2ecf20Sopenharmony_ci offset = 10; 2358c2ecf20Sopenharmony_ci } 2368c2ecf20Sopenharmony_ci packet += offset; 2378c2ecf20Sopenharmony_ci memcpy(packet, "Page: 100 Row: 10", 17); 2388c2ecf20Sopenharmony_ci packet[7] = '0' + frame / 10; 2398c2ecf20Sopenharmony_ci packet[8] = '0' + frame % 10; 2408c2ecf20Sopenharmony_ci packet[15] = '0' + line / 10; 2418c2ecf20Sopenharmony_ci packet[16] = '0' + line % 10; 2428c2ecf20Sopenharmony_ci for (i = 0; i < 42 - offset; i++) 2438c2ecf20Sopenharmony_ci packet[i] = calc_parity(packet[i]); 2448c2ecf20Sopenharmony_ci} 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_civoid vivid_vbi_gen_sliced(struct vivid_vbi_gen_data *vbi, 2478c2ecf20Sopenharmony_ci bool is_60hz, unsigned seqnr) 2488c2ecf20Sopenharmony_ci{ 2498c2ecf20Sopenharmony_ci struct v4l2_sliced_vbi_data *data0 = vbi->data; 2508c2ecf20Sopenharmony_ci struct v4l2_sliced_vbi_data *data1 = vbi->data + 1; 2518c2ecf20Sopenharmony_ci unsigned frame = seqnr % 60; 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci memset(vbi->data, 0, sizeof(vbi->data)); 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci if (!is_60hz) { 2568c2ecf20Sopenharmony_ci unsigned i; 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci for (i = 0; i <= 11; i++) { 2598c2ecf20Sopenharmony_ci data0->id = V4L2_SLICED_TELETEXT_B; 2608c2ecf20Sopenharmony_ci data0->line = 7 + i; 2618c2ecf20Sopenharmony_ci vivid_vbi_gen_teletext(data0->data, i, frame); 2628c2ecf20Sopenharmony_ci data0++; 2638c2ecf20Sopenharmony_ci } 2648c2ecf20Sopenharmony_ci data0->id = V4L2_SLICED_WSS_625; 2658c2ecf20Sopenharmony_ci data0->line = 23; 2668c2ecf20Sopenharmony_ci /* 4x3 video aspect ratio */ 2678c2ecf20Sopenharmony_ci data0->data[0] = 0x08; 2688c2ecf20Sopenharmony_ci data0++; 2698c2ecf20Sopenharmony_ci for (i = 0; i <= 11; i++) { 2708c2ecf20Sopenharmony_ci data0->id = V4L2_SLICED_TELETEXT_B; 2718c2ecf20Sopenharmony_ci data0->field = 1; 2728c2ecf20Sopenharmony_ci data0->line = 7 + i; 2738c2ecf20Sopenharmony_ci vivid_vbi_gen_teletext(data0->data, 12 + i, frame); 2748c2ecf20Sopenharmony_ci data0++; 2758c2ecf20Sopenharmony_ci } 2768c2ecf20Sopenharmony_ci return; 2778c2ecf20Sopenharmony_ci } 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci data0->id = V4L2_SLICED_CAPTION_525; 2808c2ecf20Sopenharmony_ci data0->line = 21; 2818c2ecf20Sopenharmony_ci data1->id = V4L2_SLICED_CAPTION_525; 2828c2ecf20Sopenharmony_ci data1->field = 1; 2838c2ecf20Sopenharmony_ci data1->line = 21; 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci if (frame < 15) { 2868c2ecf20Sopenharmony_ci data0->data[0] = calc_parity(vivid_cc_sequence1[2 * frame]); 2878c2ecf20Sopenharmony_ci data0->data[1] = calc_parity(vivid_cc_sequence1[2 * frame + 1]); 2888c2ecf20Sopenharmony_ci } else if (frame >= 30 && frame < 45) { 2898c2ecf20Sopenharmony_ci frame -= 30; 2908c2ecf20Sopenharmony_ci data0->data[0] = calc_parity(vivid_cc_sequence2[2 * frame]); 2918c2ecf20Sopenharmony_ci data0->data[1] = calc_parity(vivid_cc_sequence2[2 * frame + 1]); 2928c2ecf20Sopenharmony_ci } else { 2938c2ecf20Sopenharmony_ci data0->data[0] = calc_parity(0); 2948c2ecf20Sopenharmony_ci data0->data[1] = calc_parity(0); 2958c2ecf20Sopenharmony_ci } 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci frame = seqnr % (30 * 60); 2988c2ecf20Sopenharmony_ci switch (frame) { 2998c2ecf20Sopenharmony_ci case 0: 3008c2ecf20Sopenharmony_ci vivid_vbi_gen_set_time_of_day(vbi->time_of_day_packet); 3018c2ecf20Sopenharmony_ci fallthrough; 3028c2ecf20Sopenharmony_ci case 1 ... 7: 3038c2ecf20Sopenharmony_ci data1->data[0] = vbi->time_of_day_packet[frame * 2]; 3048c2ecf20Sopenharmony_ci data1->data[1] = vbi->time_of_day_packet[frame * 2 + 1]; 3058c2ecf20Sopenharmony_ci break; 3068c2ecf20Sopenharmony_ci default: 3078c2ecf20Sopenharmony_ci data1->data[0] = calc_parity(0); 3088c2ecf20Sopenharmony_ci data1->data[1] = calc_parity(0); 3098c2ecf20Sopenharmony_ci break; 3108c2ecf20Sopenharmony_ci } 3118c2ecf20Sopenharmony_ci} 312