brintos

brintos / linux-shallow public Read only

0
0
Text · 14.1 KiB · c373152 Raw
578 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 *   ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips4 *5 *	Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz>6 */7 8#include <linux/delay.h>9#include <linux/module.h>10#include <linux/init.h>11#include <linux/slab.h>12#include <linux/sched.h>13#include <asm/io.h>14#include <media/v4l2-device.h>15#include <media/v4l2-dev.h>16#include <media/v4l2-fh.h>17#include <media/v4l2-ioctl.h>18#include <media/v4l2-event.h>19#include <media/drv-intf/tea575x.h>20 21MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");22MODULE_DESCRIPTION("Routines for control of TEA5757/5759 Philips AM/FM radio tuner chips");23MODULE_LICENSE("GPL");24 25/*26 * definitions27 */28 29#define TEA575X_BIT_SEARCH	(1<<24)		/* 1 = search action, 0 = tuned */30#define TEA575X_BIT_UPDOWN	(1<<23)		/* 0 = search down, 1 = search up */31#define TEA575X_BIT_MONO	(1<<22)		/* 0 = stereo, 1 = mono */32#define TEA575X_BIT_BAND_MASK	(3<<20)33#define TEA575X_BIT_BAND_FM	(0<<20)34#define TEA575X_BIT_BAND_MW	(1<<20)35#define TEA575X_BIT_BAND_LW	(2<<20)36#define TEA575X_BIT_BAND_SW	(3<<20)37#define TEA575X_BIT_PORT_0	(1<<19)		/* user bit */38#define TEA575X_BIT_PORT_1	(1<<18)		/* user bit */39#define TEA575X_BIT_SEARCH_MASK	(3<<16)		/* search level */40#define TEA575X_BIT_SEARCH_5_28	     (0<<16)	/* FM >5uV, AM >28uV */41#define TEA575X_BIT_SEARCH_10_40     (1<<16)	/* FM >10uV, AM > 40uV */42#define TEA575X_BIT_SEARCH_30_63     (2<<16)	/* FM >30uV, AM > 63uV */43#define TEA575X_BIT_SEARCH_150_1000  (3<<16)	/* FM > 150uV, AM > 1000uV */44#define TEA575X_BIT_DUMMY	(1<<15)		/* buffer */45#define TEA575X_BIT_FREQ_MASK	0x7fff46 47enum { BAND_FM, BAND_FM_JAPAN, BAND_AM };48 49static const struct v4l2_frequency_band bands[] = {50	{51		.type = V4L2_TUNER_RADIO,52		.index = 0,53		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |54			      V4L2_TUNER_CAP_FREQ_BANDS,55		.rangelow   =  87500 * 16,56		.rangehigh  = 108000 * 16,57		.modulation = V4L2_BAND_MODULATION_FM,58	},59	{60		.type = V4L2_TUNER_RADIO,61		.index = 0,62		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |63			      V4L2_TUNER_CAP_FREQ_BANDS,64		.rangelow   = 76000 * 16,65		.rangehigh  = 91000 * 16,66		.modulation = V4L2_BAND_MODULATION_FM,67	},68	{69		.type = V4L2_TUNER_RADIO,70		.index = 1,71		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS,72		.rangelow   =  530 * 16,73		.rangehigh  = 1710 * 16,74		.modulation = V4L2_BAND_MODULATION_AM,75	},76};77 78/*79 * lowlevel part80 */81 82static void snd_tea575x_write(struct snd_tea575x *tea, unsigned int val)83{84	u16 l;85	u8 data;86 87	if (tea->ops->write_val)88		return tea->ops->write_val(tea, val);89 90	tea->ops->set_direction(tea, 1);91	udelay(16);92 93	for (l = 25; l > 0; l--) {94		data = (val >> 24) & TEA575X_DATA;95		val <<= 1;			/* shift data */96		tea->ops->set_pins(tea, data | TEA575X_WREN);97		udelay(2);98		tea->ops->set_pins(tea, data | TEA575X_WREN | TEA575X_CLK);99		udelay(2);100		tea->ops->set_pins(tea, data | TEA575X_WREN);101		udelay(2);102	}103 104	if (!tea->mute)105		tea->ops->set_pins(tea, 0);106}107 108static u32 snd_tea575x_read(struct snd_tea575x *tea)109{110	u16 l, rdata;111	u32 data = 0;112 113	if (tea->ops->read_val)114		return tea->ops->read_val(tea);115 116	tea->ops->set_direction(tea, 0);117	tea->ops->set_pins(tea, 0);118	udelay(16);119 120	for (l = 24; l--;) {121		tea->ops->set_pins(tea, TEA575X_CLK);122		udelay(2);123		if (!l)124			tea->tuned = tea->ops->get_pins(tea) & TEA575X_MOST ? 0 : 1;125		tea->ops->set_pins(tea, 0);126		udelay(2);127		data <<= 1;			/* shift data */128		rdata = tea->ops->get_pins(tea);129		if (!l)130			tea->stereo = (rdata & TEA575X_MOST) ?  0 : 1;131		if (rdata & TEA575X_DATA)132			data++;133		udelay(2);134	}135 136	if (tea->mute)137		tea->ops->set_pins(tea, TEA575X_WREN);138 139	return data;140}141 142static u32 snd_tea575x_val_to_freq(struct snd_tea575x *tea, u32 val)143{144	u32 freq = val & TEA575X_BIT_FREQ_MASK;145 146	if (freq == 0)147		return freq;148 149	switch (tea->band) {150	case BAND_FM:151		/* freq *= 12.5 */152		freq *= 125;153		freq /= 10;154		/* crystal fixup */155		freq -= TEA575X_FMIF;156		break;157	case BAND_FM_JAPAN:158		/* freq *= 12.5 */159		freq *= 125;160		freq /= 10;161		/* crystal fixup */162		freq += TEA575X_FMIF;163		break;164	case BAND_AM:165		/* crystal fixup */166		freq -= TEA575X_AMIF;167		break;168	}169 170	return clamp(freq * 16, bands[tea->band].rangelow,171				bands[tea->band].rangehigh); /* from kHz */172}173 174static u32 snd_tea575x_get_freq(struct snd_tea575x *tea)175{176	return snd_tea575x_val_to_freq(tea, snd_tea575x_read(tea));177}178 179void snd_tea575x_set_freq(struct snd_tea575x *tea)180{181	u32 freq = tea->freq / 16;	/* to kHz */182	u32 band = 0;183 184	switch (tea->band) {185	case BAND_FM:186		band = TEA575X_BIT_BAND_FM;187		/* crystal fixup */188		freq += TEA575X_FMIF;189		/* freq /= 12.5 */190		freq *= 10;191		freq /= 125;192		break;193	case BAND_FM_JAPAN:194		band = TEA575X_BIT_BAND_FM;195		/* crystal fixup */196		freq -= TEA575X_FMIF;197		/* freq /= 12.5 */198		freq *= 10;199		freq /= 125;200		break;201	case BAND_AM:202		band = TEA575X_BIT_BAND_MW;203		/* crystal fixup */204		freq += TEA575X_AMIF;205		break;206	}207 208	tea->val &= ~(TEA575X_BIT_FREQ_MASK | TEA575X_BIT_BAND_MASK);209	tea->val |= band;210	tea->val |= freq & TEA575X_BIT_FREQ_MASK;211	snd_tea575x_write(tea, tea->val);212	tea->freq = snd_tea575x_val_to_freq(tea, tea->val);213}214EXPORT_SYMBOL(snd_tea575x_set_freq);215 216/*217 * Linux Video interface218 */219 220static int vidioc_querycap(struct file *file, void  *priv,221					struct v4l2_capability *v)222{223	struct snd_tea575x *tea = video_drvdata(file);224 225	strscpy(v->driver, tea->v4l2_dev->name, sizeof(v->driver));226	strscpy(v->card, tea->card, sizeof(v->card));227	strlcat(v->card, tea->tea5759 ? " TEA5759" : " TEA5757", sizeof(v->card));228	strscpy(v->bus_info, tea->bus_info, sizeof(v->bus_info));229	return 0;230}231 232int snd_tea575x_enum_freq_bands(struct snd_tea575x *tea,233					struct v4l2_frequency_band *band)234{235	int index;236 237	if (band->tuner != 0)238		return -EINVAL;239 240	switch (band->index) {241	case 0:242		if (tea->tea5759)243			index = BAND_FM_JAPAN;244		else245			index = BAND_FM;246		break;247	case 1:248		if (tea->has_am) {249			index = BAND_AM;250			break;251		}252		fallthrough;253	default:254		return -EINVAL;255	}256 257	*band = bands[index];258	if (!tea->cannot_read_data)259		band->capability |= V4L2_TUNER_CAP_HWSEEK_BOUNDED;260 261	return 0;262}263EXPORT_SYMBOL(snd_tea575x_enum_freq_bands);264 265static int vidioc_enum_freq_bands(struct file *file, void *priv,266					 struct v4l2_frequency_band *band)267{268	struct snd_tea575x *tea = video_drvdata(file);269 270	return snd_tea575x_enum_freq_bands(tea, band);271}272 273int snd_tea575x_g_tuner(struct snd_tea575x *tea, struct v4l2_tuner *v)274{275	struct v4l2_frequency_band band_fm = { 0, };276 277	if (v->index > 0)278		return -EINVAL;279 280	snd_tea575x_read(tea);281	snd_tea575x_enum_freq_bands(tea, &band_fm);282 283	memset(v, 0, sizeof(*v));284	strscpy(v->name, tea->has_am ? "FM/AM" : "FM", sizeof(v->name));285	v->type = V4L2_TUNER_RADIO;286	v->capability = band_fm.capability;287	v->rangelow = tea->has_am ? bands[BAND_AM].rangelow : band_fm.rangelow;288	v->rangehigh = band_fm.rangehigh;289	v->rxsubchans = tea->stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO;290	v->audmode = (tea->val & TEA575X_BIT_MONO) ?291		V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO;292	v->signal = tea->tuned ? 0xffff : 0;293	return 0;294}295EXPORT_SYMBOL(snd_tea575x_g_tuner);296 297static int vidioc_g_tuner(struct file *file, void *priv,298					struct v4l2_tuner *v)299{300	struct snd_tea575x *tea = video_drvdata(file);301 302	return snd_tea575x_g_tuner(tea, v);303}304 305static int vidioc_s_tuner(struct file *file, void *priv,306					const struct v4l2_tuner *v)307{308	struct snd_tea575x *tea = video_drvdata(file);309	u32 orig_val = tea->val;310 311	if (v->index)312		return -EINVAL;313	tea->val &= ~TEA575X_BIT_MONO;314	if (v->audmode == V4L2_TUNER_MODE_MONO)315		tea->val |= TEA575X_BIT_MONO;316	/* Only apply changes if currently tuning FM */317	if (tea->band != BAND_AM && tea->val != orig_val)318		snd_tea575x_set_freq(tea);319 320	return 0;321}322 323static int vidioc_g_frequency(struct file *file, void *priv,324					struct v4l2_frequency *f)325{326	struct snd_tea575x *tea = video_drvdata(file);327 328	if (f->tuner != 0)329		return -EINVAL;330	f->type = V4L2_TUNER_RADIO;331	f->frequency = tea->freq;332	return 0;333}334 335static int vidioc_s_frequency(struct file *file, void *priv,336					const struct v4l2_frequency *f)337{338	struct snd_tea575x *tea = video_drvdata(file);339 340	if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)341		return -EINVAL;342 343	if (tea->has_am && f->frequency < (20000 * 16))344		tea->band = BAND_AM;345	else if (tea->tea5759)346		tea->band = BAND_FM_JAPAN;347	else348		tea->band = BAND_FM;349 350	tea->freq = clamp_t(u32, f->frequency, bands[tea->band].rangelow,351					bands[tea->band].rangehigh);352	snd_tea575x_set_freq(tea);353	return 0;354}355 356int snd_tea575x_s_hw_freq_seek(struct file *file, struct snd_tea575x *tea,357				const struct v4l2_hw_freq_seek *a)358{359	unsigned long timeout;360	int i, spacing;361 362	if (tea->cannot_read_data)363		return -ENOTTY;364	if (a->tuner || a->wrap_around)365		return -EINVAL;366 367	if (file->f_flags & O_NONBLOCK)368		return -EWOULDBLOCK;369 370	if (a->rangelow || a->rangehigh) {371		for (i = 0; i < ARRAY_SIZE(bands); i++) {372			if ((i == BAND_FM && tea->tea5759) ||373			    (i == BAND_FM_JAPAN && !tea->tea5759) ||374			    (i == BAND_AM && !tea->has_am))375				continue;376			if (bands[i].rangelow  == a->rangelow &&377			    bands[i].rangehigh == a->rangehigh)378				break;379		}380		if (i == ARRAY_SIZE(bands))381			return -EINVAL; /* No matching band found */382		if (i != tea->band) {383			tea->band = i;384			tea->freq = clamp(tea->freq, bands[i].rangelow,385						     bands[i].rangehigh);386			snd_tea575x_set_freq(tea);387		}388	}389 390	spacing = (tea->band == BAND_AM) ? 5 : 50; /* kHz */391 392	/* clear the frequency, HW will fill it in */393	tea->val &= ~TEA575X_BIT_FREQ_MASK;394	tea->val |= TEA575X_BIT_SEARCH;395	if (a->seek_upward)396		tea->val |= TEA575X_BIT_UPDOWN;397	else398		tea->val &= ~TEA575X_BIT_UPDOWN;399	snd_tea575x_write(tea, tea->val);400	timeout = jiffies + msecs_to_jiffies(10000);401	for (;;) {402		if (time_after(jiffies, timeout))403			break;404		if (schedule_timeout_interruptible(msecs_to_jiffies(10))) {405			/* some signal arrived, stop search */406			tea->val &= ~TEA575X_BIT_SEARCH;407			snd_tea575x_set_freq(tea);408			return -ERESTARTSYS;409		}410		if (!(snd_tea575x_read(tea) & TEA575X_BIT_SEARCH)) {411			u32 freq;412 413			/* Found a frequency, wait until it can be read */414			for (i = 0; i < 100; i++) {415				msleep(10);416				freq = snd_tea575x_get_freq(tea);417				if (freq) /* available */418					break;419			}420			if (freq == 0) /* shouldn't happen */421				break;422			/*423			 * if we moved by less than the spacing, or in the424			 * wrong direction, continue seeking425			 */426			if (abs(tea->freq - freq) < 16 * spacing ||427					(a->seek_upward && freq < tea->freq) ||428					(!a->seek_upward && freq > tea->freq)) {429				snd_tea575x_write(tea, tea->val);430				continue;431			}432			tea->freq = freq;433			tea->val &= ~TEA575X_BIT_SEARCH;434			return 0;435		}436	}437	tea->val &= ~TEA575X_BIT_SEARCH;438	snd_tea575x_set_freq(tea);439	return -ENODATA;440}441EXPORT_SYMBOL(snd_tea575x_s_hw_freq_seek);442 443static int vidioc_s_hw_freq_seek(struct file *file, void *fh,444					const struct v4l2_hw_freq_seek *a)445{446	struct snd_tea575x *tea = video_drvdata(file);447 448	return snd_tea575x_s_hw_freq_seek(file, tea, a);449}450 451static int tea575x_s_ctrl(struct v4l2_ctrl *ctrl)452{453	struct snd_tea575x *tea = container_of(ctrl->handler, struct snd_tea575x, ctrl_handler);454 455	switch (ctrl->id) {456	case V4L2_CID_AUDIO_MUTE:457		tea->mute = ctrl->val;458		snd_tea575x_set_freq(tea);459		return 0;460	}461 462	return -EINVAL;463}464 465static const struct v4l2_file_operations tea575x_fops = {466	.unlocked_ioctl	= video_ioctl2,467	.open           = v4l2_fh_open,468	.release        = v4l2_fh_release,469	.poll           = v4l2_ctrl_poll,470};471 472static const struct v4l2_ioctl_ops tea575x_ioctl_ops = {473	.vidioc_querycap    = vidioc_querycap,474	.vidioc_g_tuner     = vidioc_g_tuner,475	.vidioc_s_tuner     = vidioc_s_tuner,476	.vidioc_g_frequency = vidioc_g_frequency,477	.vidioc_s_frequency = vidioc_s_frequency,478	.vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek,479	.vidioc_enum_freq_bands = vidioc_enum_freq_bands,480	.vidioc_log_status  = v4l2_ctrl_log_status,481	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,482	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,483};484 485static const struct video_device tea575x_radio = {486	.ioctl_ops	= &tea575x_ioctl_ops,487	.release        = video_device_release_empty,488};489 490static const struct v4l2_ctrl_ops tea575x_ctrl_ops = {491	.s_ctrl = tea575x_s_ctrl,492};493 494 495int snd_tea575x_hw_init(struct snd_tea575x *tea)496{497	tea->mute = true;498 499	/* Not all devices can or know how to read the data back.500	   Such devices can set cannot_read_data to true. */501	if (!tea->cannot_read_data) {502		snd_tea575x_write(tea, 0x55AA);503		if (snd_tea575x_read(tea) != 0x55AA)504			return -ENODEV;505	}506 507	tea->val = TEA575X_BIT_BAND_FM | TEA575X_BIT_SEARCH_5_28;508	tea->freq = 90500 * 16;		/* 90.5Mhz default */509	snd_tea575x_set_freq(tea);510 511	return 0;512}513EXPORT_SYMBOL(snd_tea575x_hw_init);514 515int snd_tea575x_init(struct snd_tea575x *tea, struct module *owner)516{517	int retval = snd_tea575x_hw_init(tea);518 519	if (retval)520		return retval;521 522	tea->vd = tea575x_radio;523	video_set_drvdata(&tea->vd, tea);524	mutex_init(&tea->mutex);525	strscpy(tea->vd.name, tea->v4l2_dev->name, sizeof(tea->vd.name));526	tea->vd.lock = &tea->mutex;527	tea->vd.v4l2_dev = tea->v4l2_dev;528	tea->vd.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;529	if (!tea->cannot_read_data)530		tea->vd.device_caps |= V4L2_CAP_HW_FREQ_SEEK;531	tea->fops = tea575x_fops;532	tea->fops.owner = owner;533	tea->vd.fops = &tea->fops;534	/* disable hw_freq_seek if we can't use it */535	if (tea->cannot_read_data)536		v4l2_disable_ioctl(&tea->vd, VIDIOC_S_HW_FREQ_SEEK);537 538	if (!tea->cannot_mute) {539		tea->vd.ctrl_handler = &tea->ctrl_handler;540		v4l2_ctrl_handler_init(&tea->ctrl_handler, 1);541		v4l2_ctrl_new_std(&tea->ctrl_handler, &tea575x_ctrl_ops,542				  V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);543		retval = tea->ctrl_handler.error;544		if (retval) {545			v4l2_err(tea->v4l2_dev, "can't initialize controls\n");546			v4l2_ctrl_handler_free(&tea->ctrl_handler);547			return retval;548		}549 550		if (tea->ext_init) {551			retval = tea->ext_init(tea);552			if (retval) {553				v4l2_ctrl_handler_free(&tea->ctrl_handler);554				return retval;555			}556		}557 558		v4l2_ctrl_handler_setup(&tea->ctrl_handler);559	}560 561	retval = video_register_device(&tea->vd, VFL_TYPE_RADIO, tea->radio_nr);562	if (retval) {563		v4l2_err(tea->v4l2_dev, "can't register video device!\n");564		v4l2_ctrl_handler_free(tea->vd.ctrl_handler);565		return retval;566	}567 568	return 0;569}570EXPORT_SYMBOL(snd_tea575x_init);571 572void snd_tea575x_exit(struct snd_tea575x *tea)573{574	video_unregister_device(&tea->vd);575	v4l2_ctrl_handler_free(tea->vd.ctrl_handler);576}577EXPORT_SYMBOL(snd_tea575x_exit);578