18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * vivid-rds-gen.c - rds (radio data system) 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/kernel.h>
98c2ecf20Sopenharmony_ci#include <linux/ktime.h>
108c2ecf20Sopenharmony_ci#include <linux/string.h>
118c2ecf20Sopenharmony_ci#include <linux/videodev2.h>
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include "vivid-rds-gen.h"
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_cistatic u8 vivid_get_di(const struct vivid_rds_gen *rds, unsigned grp)
168c2ecf20Sopenharmony_ci{
178c2ecf20Sopenharmony_ci	switch (grp) {
188c2ecf20Sopenharmony_ci	case 0:
198c2ecf20Sopenharmony_ci		return (rds->dyn_pty << 2) | (grp & 3);
208c2ecf20Sopenharmony_ci	case 1:
218c2ecf20Sopenharmony_ci		return (rds->compressed << 2) | (grp & 3);
228c2ecf20Sopenharmony_ci	case 2:
238c2ecf20Sopenharmony_ci		return (rds->art_head << 2) | (grp & 3);
248c2ecf20Sopenharmony_ci	case 3:
258c2ecf20Sopenharmony_ci		return (rds->mono_stereo << 2) | (grp & 3);
268c2ecf20Sopenharmony_ci	}
278c2ecf20Sopenharmony_ci	return 0;
288c2ecf20Sopenharmony_ci}
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci/*
318c2ecf20Sopenharmony_ci * This RDS generator creates 57 RDS groups (one group == four RDS blocks).
328c2ecf20Sopenharmony_ci * Groups 0-3, 22-25 and 44-47 (spaced 22 groups apart) are filled with a
338c2ecf20Sopenharmony_ci * standard 0B group containing the PI code and PS name.
348c2ecf20Sopenharmony_ci *
358c2ecf20Sopenharmony_ci * Groups 4-19 and 26-41 use group 2A for the radio text.
368c2ecf20Sopenharmony_ci *
378c2ecf20Sopenharmony_ci * Group 56 contains the time (group 4A).
388c2ecf20Sopenharmony_ci *
398c2ecf20Sopenharmony_ci * All remaining groups use a filler group 15B block that just repeats
408c2ecf20Sopenharmony_ci * the PI and PTY codes.
418c2ecf20Sopenharmony_ci */
428c2ecf20Sopenharmony_civoid vivid_rds_generate(struct vivid_rds_gen *rds)
438c2ecf20Sopenharmony_ci{
448c2ecf20Sopenharmony_ci	struct v4l2_rds_data *data = rds->data;
458c2ecf20Sopenharmony_ci	unsigned grp;
468c2ecf20Sopenharmony_ci	unsigned idx;
478c2ecf20Sopenharmony_ci	struct tm tm;
488c2ecf20Sopenharmony_ci	unsigned date;
498c2ecf20Sopenharmony_ci	unsigned time;
508c2ecf20Sopenharmony_ci	int l;
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	for (grp = 0; grp < VIVID_RDS_GEN_GROUPS; grp++, data += VIVID_RDS_GEN_BLKS_PER_GRP) {
538c2ecf20Sopenharmony_ci		data[0].lsb = rds->picode & 0xff;
548c2ecf20Sopenharmony_ci		data[0].msb = rds->picode >> 8;
558c2ecf20Sopenharmony_ci		data[0].block = V4L2_RDS_BLOCK_A | (V4L2_RDS_BLOCK_A << 3);
568c2ecf20Sopenharmony_ci		data[1].lsb = rds->pty << 5;
578c2ecf20Sopenharmony_ci		data[1].msb = (rds->pty >> 3) | (rds->tp << 2);
588c2ecf20Sopenharmony_ci		data[1].block = V4L2_RDS_BLOCK_B | (V4L2_RDS_BLOCK_B << 3);
598c2ecf20Sopenharmony_ci		data[3].block = V4L2_RDS_BLOCK_D | (V4L2_RDS_BLOCK_D << 3);
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci		switch (grp) {
628c2ecf20Sopenharmony_ci		case 0 ... 3:
638c2ecf20Sopenharmony_ci		case 22 ... 25:
648c2ecf20Sopenharmony_ci		case 44 ... 47: /* Group 0B */
658c2ecf20Sopenharmony_ci			idx = (grp % 22) % 4;
668c2ecf20Sopenharmony_ci			data[1].lsb |= (rds->ta << 4) | (rds->ms << 3);
678c2ecf20Sopenharmony_ci			data[1].lsb |= vivid_get_di(rds, idx);
688c2ecf20Sopenharmony_ci			data[1].msb |= 1 << 3;
698c2ecf20Sopenharmony_ci			data[2].lsb = rds->picode & 0xff;
708c2ecf20Sopenharmony_ci			data[2].msb = rds->picode >> 8;
718c2ecf20Sopenharmony_ci			data[2].block = V4L2_RDS_BLOCK_C_ALT | (V4L2_RDS_BLOCK_C_ALT << 3);
728c2ecf20Sopenharmony_ci			data[3].lsb = rds->psname[2 * idx + 1];
738c2ecf20Sopenharmony_ci			data[3].msb = rds->psname[2 * idx];
748c2ecf20Sopenharmony_ci			break;
758c2ecf20Sopenharmony_ci		case 4 ... 19:
768c2ecf20Sopenharmony_ci		case 26 ... 41: /* Group 2A */
778c2ecf20Sopenharmony_ci			idx = ((grp - 4) % 22) % 16;
788c2ecf20Sopenharmony_ci			data[1].lsb |= idx;
798c2ecf20Sopenharmony_ci			data[1].msb |= 4 << 3;
808c2ecf20Sopenharmony_ci			data[2].msb = rds->radiotext[4 * idx];
818c2ecf20Sopenharmony_ci			data[2].lsb = rds->radiotext[4 * idx + 1];
828c2ecf20Sopenharmony_ci			data[2].block = V4L2_RDS_BLOCK_C | (V4L2_RDS_BLOCK_C << 3);
838c2ecf20Sopenharmony_ci			data[3].msb = rds->radiotext[4 * idx + 2];
848c2ecf20Sopenharmony_ci			data[3].lsb = rds->radiotext[4 * idx + 3];
858c2ecf20Sopenharmony_ci			break;
868c2ecf20Sopenharmony_ci		case 56:
878c2ecf20Sopenharmony_ci			/*
888c2ecf20Sopenharmony_ci			 * Group 4A
898c2ecf20Sopenharmony_ci			 *
908c2ecf20Sopenharmony_ci			 * Uses the algorithm from Annex G of the RDS standard
918c2ecf20Sopenharmony_ci			 * EN 50067:1998 to convert a UTC date to an RDS Modified
928c2ecf20Sopenharmony_ci			 * Julian Day.
938c2ecf20Sopenharmony_ci			 */
948c2ecf20Sopenharmony_ci			time64_to_tm(ktime_get_real_seconds(), 0, &tm);
958c2ecf20Sopenharmony_ci			l = tm.tm_mon <= 1;
968c2ecf20Sopenharmony_ci			date = 14956 + tm.tm_mday + ((tm.tm_year - l) * 1461) / 4 +
978c2ecf20Sopenharmony_ci				((tm.tm_mon + 2 + l * 12) * 306001) / 10000;
988c2ecf20Sopenharmony_ci			time = (tm.tm_hour << 12) |
998c2ecf20Sopenharmony_ci			       (tm.tm_min << 6) |
1008c2ecf20Sopenharmony_ci			       (sys_tz.tz_minuteswest >= 0 ? 0x20 : 0) |
1018c2ecf20Sopenharmony_ci			       (abs(sys_tz.tz_minuteswest) / 30);
1028c2ecf20Sopenharmony_ci			data[1].lsb &= ~3;
1038c2ecf20Sopenharmony_ci			data[1].lsb |= date >> 15;
1048c2ecf20Sopenharmony_ci			data[1].msb |= 8 << 3;
1058c2ecf20Sopenharmony_ci			data[2].lsb = (date << 1) & 0xfe;
1068c2ecf20Sopenharmony_ci			data[2].lsb |= (time >> 16) & 1;
1078c2ecf20Sopenharmony_ci			data[2].msb = (date >> 7) & 0xff;
1088c2ecf20Sopenharmony_ci			data[2].block = V4L2_RDS_BLOCK_C | (V4L2_RDS_BLOCK_C << 3);
1098c2ecf20Sopenharmony_ci			data[3].lsb = time & 0xff;
1108c2ecf20Sopenharmony_ci			data[3].msb = (time >> 8) & 0xff;
1118c2ecf20Sopenharmony_ci			break;
1128c2ecf20Sopenharmony_ci		default: /* Group 15B */
1138c2ecf20Sopenharmony_ci			data[1].lsb |= (rds->ta << 4) | (rds->ms << 3);
1148c2ecf20Sopenharmony_ci			data[1].lsb |= vivid_get_di(rds, grp % 22);
1158c2ecf20Sopenharmony_ci			data[1].msb |= 0x1f << 3;
1168c2ecf20Sopenharmony_ci			data[2].lsb = rds->picode & 0xff;
1178c2ecf20Sopenharmony_ci			data[2].msb = rds->picode >> 8;
1188c2ecf20Sopenharmony_ci			data[2].block = V4L2_RDS_BLOCK_C_ALT | (V4L2_RDS_BLOCK_C_ALT << 3);
1198c2ecf20Sopenharmony_ci			data[3].lsb = rds->pty << 5;
1208c2ecf20Sopenharmony_ci			data[3].lsb |= (rds->ta << 4) | (rds->ms << 3);
1218c2ecf20Sopenharmony_ci			data[3].lsb |= vivid_get_di(rds, grp % 22);
1228c2ecf20Sopenharmony_ci			data[3].msb |= rds->pty >> 3;
1238c2ecf20Sopenharmony_ci			data[3].msb |= 0x1f << 3;
1248c2ecf20Sopenharmony_ci			break;
1258c2ecf20Sopenharmony_ci		}
1268c2ecf20Sopenharmony_ci	}
1278c2ecf20Sopenharmony_ci}
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_civoid vivid_rds_gen_fill(struct vivid_rds_gen *rds, unsigned freq,
1308c2ecf20Sopenharmony_ci			  bool alt)
1318c2ecf20Sopenharmony_ci{
1328c2ecf20Sopenharmony_ci	/* Alternate PTY between Info and Weather */
1338c2ecf20Sopenharmony_ci	if (rds->use_rbds) {
1348c2ecf20Sopenharmony_ci		rds->picode = 0x2e75; /* 'KLNX' call sign */
1358c2ecf20Sopenharmony_ci		rds->pty = alt ? 29 : 2;
1368c2ecf20Sopenharmony_ci	} else {
1378c2ecf20Sopenharmony_ci		rds->picode = 0x8088;
1388c2ecf20Sopenharmony_ci		rds->pty = alt ? 16 : 3;
1398c2ecf20Sopenharmony_ci	}
1408c2ecf20Sopenharmony_ci	rds->mono_stereo = true;
1418c2ecf20Sopenharmony_ci	rds->art_head = false;
1428c2ecf20Sopenharmony_ci	rds->compressed = false;
1438c2ecf20Sopenharmony_ci	rds->dyn_pty = false;
1448c2ecf20Sopenharmony_ci	rds->tp = true;
1458c2ecf20Sopenharmony_ci	rds->ta = alt;
1468c2ecf20Sopenharmony_ci	rds->ms = true;
1478c2ecf20Sopenharmony_ci	snprintf(rds->psname, sizeof(rds->psname), "%6d.%1d",
1488c2ecf20Sopenharmony_ci		 (freq / 16) % 1000000, (((freq & 0xf) * 10) / 16) % 10);
1498c2ecf20Sopenharmony_ci	if (alt)
1508c2ecf20Sopenharmony_ci		strscpy(rds->radiotext,
1518c2ecf20Sopenharmony_ci			" The Radio Data System can switch between different Radio Texts ",
1528c2ecf20Sopenharmony_ci			sizeof(rds->radiotext));
1538c2ecf20Sopenharmony_ci	else
1548c2ecf20Sopenharmony_ci		strscpy(rds->radiotext,
1558c2ecf20Sopenharmony_ci			"An example of Radio Text as transmitted by the Radio Data System",
1568c2ecf20Sopenharmony_ci			sizeof(rds->radiotext));
1578c2ecf20Sopenharmony_ci}
158