/* * vivid-rds-gen.c - rds (radio data system) generator support functions. * * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved. * * This program is free software; you may redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include <linux/kernel.h> #include <linux/ktime.h> #include <linux/string.h> #include <linux/videodev2.h> #include "vivid-rds-gen.h" static u8 vivid_get_di(const struct vivid_rds_gen *rds, unsigned grp) { switch (grp) { case 0: return (rds->dyn_pty << 2) | (grp & 3); case 1: return (rds->compressed << 2) | (grp & 3); case 2: return (rds->art_head << 2) | (grp & 3); case 3: return (rds->mono_stereo << 2) | (grp & 3); } return 0; } /* * This RDS generator creates 57 RDS groups (one group == four RDS blocks). * Groups 0-3, 22-25 and 44-47 (spaced 22 groups apart) are filled with a * standard 0B group containing the PI code and PS name. * * Groups 4-19 and 26-41 use group 2A for the radio text. * * Group 56 contains the time (group 4A). * * All remaining groups use a filler group 15B block that just repeats * the PI and PTY codes. */ void vivid_rds_generate(struct vivid_rds_gen *rds) { struct v4l2_rds_data *data = rds->data; unsigned grp; struct tm tm; unsigned date; unsigned time; int l; for (grp = 0; grp < VIVID_RDS_GEN_GROUPS; grp++, data += VIVID_RDS_GEN_BLKS_PER_GRP) { data[0].lsb = rds->picode & 0xff; data[0].msb = rds->picode >> 8; data[0].block = V4L2_RDS_BLOCK_A | (V4L2_RDS_BLOCK_A << 3); data[1].lsb = rds->pty << 5; data[1].msb = (rds->pty >> 3) | (rds->tp << 2); data[1].block = V4L2_RDS_BLOCK_B | (V4L2_RDS_BLOCK_B << 3); data[3].block = V4L2_RDS_BLOCK_D | (V4L2_RDS_BLOCK_D << 3); switch (grp) { case 0 ... 3: case 22 ... 25: case 44 ... 47: /* Group 0B */ data[1].lsb |= (rds->ta << 4) | (rds->ms << 3); data[1].lsb |= vivid_get_di(rds, grp % 22); data[1].msb |= 1 << 3; data[2].lsb = rds->picode & 0xff; data[2].msb = rds->picode >> 8; data[2].block = V4L2_RDS_BLOCK_C_ALT | (V4L2_RDS_BLOCK_C_ALT << 3); data[3].lsb = rds->psname[2 * (grp % 22) + 1]; data[3].msb = rds->psname[2 * (grp % 22)]; break; case 4 ... 19: case 26 ... 41: /* Group 2A */ data[1].lsb |= (grp - 4) % 22; data[1].msb |= 4 << 3; data[2].msb = rds->radiotext[4 * ((grp - 4) % 22)]; data[2].lsb = rds->radiotext[4 * ((grp - 4) % 22) + 1]; data[2].block = V4L2_RDS_BLOCK_C | (V4L2_RDS_BLOCK_C << 3); data[3].msb = rds->radiotext[4 * ((grp - 4) % 22) + 2]; data[3].lsb = rds->radiotext[4 * ((grp - 4) % 22) + 3]; break; case 56: /* * Group 4A * * Uses the algorithm from Annex G of the RDS standard * EN 50067:1998 to convert a UTC date to an RDS Modified * Julian Day. */ time_to_tm(get_seconds(), 0, &tm); l = tm.tm_mon <= 1; date = 14956 + tm.tm_mday + ((tm.tm_year - l) * 1461) / 4 + ((tm.tm_mon + 2 + l * 12) * 306001) / 10000; time = (tm.tm_hour << 12) | (tm.tm_min << 6) | (sys_tz.tz_minuteswest >= 0 ? 0x20 : 0) | (abs(sys_tz.tz_minuteswest) / 30); data[1].lsb &= ~3; data[1].lsb |= date >> 15; data[1].msb |= 8 << 3; data[2].lsb = (date << 1) & 0xfe; data[2].lsb |= (time >> 16) & 1; data[2].msb = (date >> 7) & 0xff; data[2].block = V4L2_RDS_BLOCK_C | (V4L2_RDS_BLOCK_C << 3); data[3].lsb = time & 0xff; data[3].msb = (time >> 8) & 0xff; break; default: /* Group 15B */ data[1].lsb |= (rds->ta << 4) | (rds->ms << 3); data[1].lsb |= vivid_get_di(rds, grp % 22); data[1].msb |= 0x1f << 3; data[2].lsb = rds->picode & 0xff; data[2].msb = rds->picode >> 8; data[2].block = V4L2_RDS_BLOCK_C_ALT | (V4L2_RDS_BLOCK_C_ALT << 3); data[3].lsb = rds->pty << 5; data[3].lsb |= (rds->ta << 4) | (rds->ms << 3); data[3].lsb |= vivid_get_di(rds, grp % 22); data[3].msb |= rds->pty >> 3; data[3].msb |= 0x1f << 3; break; } } } void vivid_rds_gen_fill(struct vivid_rds_gen *rds, unsigned freq, bool alt) { /* Alternate PTY between Info and Weather */ if (rds->use_rbds) { rds->picode = 0x2e75; /* 'KLNX' call sign */ rds->pty = alt ? 29 : 2; } else { rds->picode = 0x8088; rds->pty = alt ? 16 : 3; } rds->mono_stereo = true; rds->art_head = false; rds->compressed = false; rds->dyn_pty = false; rds->tp = true; rds->ta = alt; rds->ms = true; snprintf(rds->psname, sizeof(rds->psname), "%6d.%1d", freq / 16, ((freq & 0xf) * 10) / 16); if (alt) strlcpy(rds->radiotext, " The Radio Data System can switch between different Radio Texts ", sizeof(rds->radiotext)); else strlcpy(rds->radiotext, "An example of Radio Text as transmitted by the Radio Data System", sizeof(rds->radiotext)); }