Blame view

sound/pci/hda/hda_beep.c 6.73 KB
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  /*
   * Digital Beep Input Interface for HD-audio codec
   *
   * Author: Matthew Ranostay <mranostay@embeddedalley.com>
   * Copyright (c) 2008 Embedded Alley Solutions Inc
   *
   *  This driver is free software; you can redistribute it and/or modify
   *  it under the terms of the GNU General Public License as published by
   *  the Free Software Foundation; either version 2 of the License, or
   *  (at your option) any later version.
   *
   *  This driver is distributed in the hope that it will be useful,
   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *  GNU General Public License for more details.
   *
   *  You should have received a copy of the GNU General Public License
   *  along with this program; if not, write to the Free Software
   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
   */
  
  #include <linux/input.h>
  #include <linux/pci.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
24
  #include <linux/slab.h>
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
25
  #include <linux/workqueue.h>
d81a6d717   Paul Gortmaker   sound: Add export...
26
  #include <linux/export.h>
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
27
28
  #include <sound/core.h>
  #include "hda_beep.h"
b7b51141b   Takashi Iwai   ALSA: hda - Check...
29
  #include "hda_local.h"
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
30
31
32
33
34
35
36
37
38
39
40
41
  
  enum {
  	DIGBEEP_HZ_STEP = 46875,	/* 46.875 Hz */
  	DIGBEEP_HZ_MIN = 93750,		/* 93.750 Hz */
  	DIGBEEP_HZ_MAX = 12000000,	/* 12 KHz */
  };
  
  static void snd_hda_generate_beep(struct work_struct *work)
  {
  	struct hda_beep *beep =
  		container_of(work, struct hda_beep, beep_work);
  	struct hda_codec *codec = beep->codec;
4d4e9bb33   Takashi Iwai   ALSA: hda - Add d...
42
43
  	if (!beep->enabled)
  		return;
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
44
  	/* generate tone */
411fe85c7   Takashi Iwai   ALSA: hda - Don't...
45
  	snd_hda_codec_write(codec, beep->nid, 0,
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
46
47
  			AC_VERB_SET_BEEP_CONTROL, beep->tone);
  }
fa7979663   Takashi Iwai   ALSA: hda - Fix d...
48
49
50
51
52
  /* (non-standard) Linear beep tone calculation for IDT/STAC codecs 
   *
   * The tone frequency of beep generator on IDT/STAC codecs is
   * defined from the 8bit tone parameter, in Hz,
   *    freq = 48000 * (257 - tone) / 1024
369693dc9   Paul Vojta   ALSA: hda - fix b...
53
   * that is from 12kHz to 93.75Hz in steps of 46.875 Hz
fa7979663   Takashi Iwai   ALSA: hda - Fix d...
54
55
56
   */
  static int beep_linear_tone(struct hda_beep *beep, int hz)
  {
369693dc9   Paul Vojta   ALSA: hda - fix b...
57
58
  	if (hz <= 0)
  		return 0;
fa7979663   Takashi Iwai   ALSA: hda - Fix d...
59
  	hz *= 1000; /* fixed point */
369693dc9   Paul Vojta   ALSA: hda - fix b...
60
61
  	hz = hz - DIGBEEP_HZ_MIN
  		+ DIGBEEP_HZ_STEP / 2; /* round to nearest step */
fa7979663   Takashi Iwai   ALSA: hda - Fix d...
62
63
64
  	if (hz < 0)
  		hz = 0; /* turn off PC beep*/
  	else if (hz >= (DIGBEEP_HZ_MAX - DIGBEEP_HZ_MIN))
369693dc9   Paul Vojta   ALSA: hda - fix b...
65
  		hz = 1; /* max frequency */
fa7979663   Takashi Iwai   ALSA: hda - Fix d...
66
67
  	else {
  		hz /= DIGBEEP_HZ_STEP;
369693dc9   Paul Vojta   ALSA: hda - fix b...
68
  		hz = 255 - hz;
fa7979663   Takashi Iwai   ALSA: hda - Fix d...
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
  	}
  	return hz;
  }
  
  /* HD-audio standard beep tone parameter calculation
   *
   * The tone frequency in Hz is calculated as
   *   freq = 48000 / (tone * 4)
   * from 47Hz to 12kHz
   */
  static int beep_standard_tone(struct hda_beep *beep, int hz)
  {
  	if (hz <= 0)
  		return 0; /* disabled */
  	hz = 12000 / hz;
  	if (hz > 0xff)
  		return 0xff;
  	if (hz <= 0)
  		return 1;
  	return hz;
  }
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
90
91
92
93
94
95
96
97
98
99
  static int snd_hda_beep_event(struct input_dev *dev, unsigned int type,
  				unsigned int code, int hz)
  {
  	struct hda_beep *beep = input_get_drvdata(dev);
  
  	switch (code) {
  	case SND_BELL:
  		if (hz)
  			hz = 1000;
  	case SND_TONE:
fa7979663   Takashi Iwai   ALSA: hda - Fix d...
100
101
102
103
  		if (beep->linear_tone)
  			beep->tone = beep_linear_tone(beep, hz);
  		else
  			beep->tone = beep_standard_tone(beep, hz);
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
104
105
106
107
  		break;
  	default:
  		return -1;
  	}
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
108
109
110
111
112
  
  	/* schedule beep event */
  	schedule_work(&beep->beep_work);
  	return 0;
  }
123c07aed   Jaroslav Kysela   ALSA: hda_intel: ...
113
114
115
116
117
118
  static void snd_hda_do_detach(struct hda_beep *beep)
  {
  	input_unregister_device(beep->dev);
  	beep->dev = NULL;
  	cancel_work_sync(&beep->beep_work);
  	/* turn off beep for sure */
411fe85c7   Takashi Iwai   ALSA: hda - Don't...
119
  	snd_hda_codec_write(beep->codec, beep->nid, 0,
123c07aed   Jaroslav Kysela   ALSA: hda_intel: ...
120
121
122
123
  				  AC_VERB_SET_BEEP_CONTROL, 0);
  }
  
  static int snd_hda_do_attach(struct hda_beep *beep)
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
124
125
  {
  	struct input_dev *input_dev;
123c07aed   Jaroslav Kysela   ALSA: hda_intel: ...
126
  	struct hda_codec *codec = beep->codec;
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
127
  	int err;
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
128
  	input_dev = input_allocate_device();
6a12afb56   Takashi Iwai   ALSA: hda - Missi...
129
  	if (!input_dev) {
123c07aed   Jaroslav Kysela   ALSA: hda_intel: ...
130
131
  		printk(KERN_INFO "hda_beep: unable to allocate input device
  ");
6a12afb56   Takashi Iwai   ALSA: hda - Missi...
132
133
  		return -ENOMEM;
  	}
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
  
  	/* setup digital beep device */
  	input_dev->name = "HDA Digital PCBeep";
  	input_dev->phys = beep->phys;
  	input_dev->id.bustype = BUS_PCI;
  
  	input_dev->id.vendor = codec->vendor_id >> 16;
  	input_dev->id.product = codec->vendor_id & 0xffff;
  	input_dev->id.version = 0x01;
  
  	input_dev->evbit[0] = BIT_MASK(EV_SND);
  	input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE);
  	input_dev->event = snd_hda_beep_event;
  	input_dev->dev.parent = &codec->bus->pci->dev;
  	input_set_drvdata(input_dev, beep);
  
  	err = input_register_device(input_dev);
  	if (err < 0) {
f6154d6d0   Takashi Iwai   ALSA: hda - use i...
152
  		input_free_device(input_dev);
123c07aed   Jaroslav Kysela   ALSA: hda_intel: ...
153
154
  		printk(KERN_INFO "hda_beep: unable to register input device
  ");
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
155
156
  		return err;
  	}
123c07aed   Jaroslav Kysela   ALSA: hda_intel: ...
157
158
159
160
161
162
163
164
  	beep->dev = input_dev;
  	return 0;
  }
  
  static void snd_hda_do_register(struct work_struct *work)
  {
  	struct hda_beep *beep =
  		container_of(work, struct hda_beep, register_work);
123c07aed   Jaroslav Kysela   ALSA: hda_intel: ...
165
166
  
  	mutex_lock(&beep->mutex);
13dab0808   Jaroslav Kysela   ALSA: hda_intel: ...
167
168
169
170
171
172
173
174
175
176
177
178
179
  	if (beep->enabled && !beep->dev)
  		snd_hda_do_attach(beep);
  	mutex_unlock(&beep->mutex);
  }
  
  static void snd_hda_do_unregister(struct work_struct *work)
  {
  	struct hda_beep *beep =
  		container_of(work, struct hda_beep, unregister_work.work);
  
  	mutex_lock(&beep->mutex);
  	if (!beep->enabled && beep->dev)
  		snd_hda_do_detach(beep);
123c07aed   Jaroslav Kysela   ALSA: hda_intel: ...
180
181
182
183
184
185
186
  	mutex_unlock(&beep->mutex);
  }
  
  int snd_hda_enable_beep_device(struct hda_codec *codec, int enable)
  {
  	struct hda_beep *beep = codec->beep;
  	enable = !!enable;
13dab0808   Jaroslav Kysela   ALSA: hda_intel: ...
187
188
189
190
  	if (beep == NULL)
  		return 0;
  	if (beep->enabled != enable) {
  		beep->enabled = enable;
2dca0bba7   Jaroslav Kysela   ALSA: hda - add b...
191
  		if (!enable) {
13dab0808   Jaroslav Kysela   ALSA: hda_intel: ...
192
  			/* turn off beep */
411fe85c7   Takashi Iwai   ALSA: hda - Don't...
193
  			snd_hda_codec_write(beep->codec, beep->nid, 0,
13dab0808   Jaroslav Kysela   ALSA: hda_intel: ...
194
  						  AC_VERB_SET_BEEP_CONTROL, 0);
2dca0bba7   Jaroslav Kysela   ALSA: hda - add b...
195
196
197
198
199
200
201
202
203
  		}
  		if (beep->mode == HDA_BEEP_MODE_SWREG) {
  			if (enable) {
  				cancel_delayed_work(&beep->unregister_work);
  				schedule_work(&beep->register_work);
  			} else {
  				schedule_delayed_work(&beep->unregister_work,
  									   HZ);
  			}
13dab0808   Jaroslav Kysela   ALSA: hda_intel: ...
204
  		}
123c07aed   Jaroslav Kysela   ALSA: hda_intel: ...
205
206
207
208
209
210
211
212
213
214
215
  		return 1;
  	}
  	return 0;
  }
  EXPORT_SYMBOL_HDA(snd_hda_enable_beep_device);
  
  int snd_hda_attach_beep_device(struct hda_codec *codec, int nid)
  {
  	struct hda_beep *beep;
  
  	if (!snd_hda_get_bool_hint(codec, "beep"))
9bb1fe390   Takashi Iwai   ALSA: hda - Fix b...
216
217
218
  		return 0; /* disabled explicitly by hints */
  	if (codec->beep_mode == HDA_BEEP_MODE_OFF)
  		return 0; /* disabled by module option */
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
219

123c07aed   Jaroslav Kysela   ALSA: hda_intel: ...
220
221
222
223
224
  	beep = kzalloc(sizeof(*beep), GFP_KERNEL);
  	if (beep == NULL)
  		return -ENOMEM;
  	snprintf(beep->phys, sizeof(beep->phys),
  		"card%d/codec#%d/beep0", codec->bus->card->number, codec->addr);
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
225
226
227
228
229
  	/* enable linear scale */
  	snd_hda_codec_write(codec, nid, 0,
  		AC_VERB_SET_DIGI_CONVERT_2, 0x01);
  
  	beep->nid = nid;
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
230
  	beep->codec = codec;
2dca0bba7   Jaroslav Kysela   ALSA: hda - add b...
231
  	beep->mode = codec->beep_mode;
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
232
  	codec->beep = beep;
123c07aed   Jaroslav Kysela   ALSA: hda_intel: ...
233
  	INIT_WORK(&beep->register_work, &snd_hda_do_register);
13dab0808   Jaroslav Kysela   ALSA: hda_intel: ...
234
  	INIT_DELAYED_WORK(&beep->unregister_work, &snd_hda_do_unregister);
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
235
  	INIT_WORK(&beep->beep_work, &snd_hda_generate_beep);
123c07aed   Jaroslav Kysela   ALSA: hda_intel: ...
236
  	mutex_init(&beep->mutex);
2dca0bba7   Jaroslav Kysela   ALSA: hda - add b...
237
  	if (beep->mode == HDA_BEEP_MODE_ON) {
54f7190b2   Takashi Iwai   ALSA: hda - Fix O...
238
239
240
241
242
243
  		int err = snd_hda_do_attach(beep);
  		if (err < 0) {
  			kfree(beep);
  			codec->beep = NULL;
  			return err;
  		}
2dca0bba7   Jaroslav Kysela   ALSA: hda - add b...
244
  	}
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
245
246
  	return 0;
  }
ff7a32673   Takashi Iwai   ALSA: hda - Don't...
247
  EXPORT_SYMBOL_HDA(snd_hda_attach_beep_device);
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
248
249
250
251
252
  
  void snd_hda_detach_beep_device(struct hda_codec *codec)
  {
  	struct hda_beep *beep = codec->beep;
  	if (beep) {
123c07aed   Jaroslav Kysela   ALSA: hda_intel: ...
253
  		cancel_work_sync(&beep->register_work);
5f8166975   Jaroslav Kysela   ALSA: hda: beep -...
254
  		cancel_delayed_work(&beep->unregister_work);
54f7190b2   Takashi Iwai   ALSA: hda - Fix O...
255
  		if (beep->dev)
123c07aed   Jaroslav Kysela   ALSA: hda_intel: ...
256
  			snd_hda_do_detach(beep);
c44765b8c   Takashi Iwai   ALSA: hda - Clear...
257
  		codec->beep = NULL;
123c07aed   Jaroslav Kysela   ALSA: hda_intel: ...
258
  		kfree(beep);
1cd2224cd   Matthew Ranostay   ALSA: hda: digita...
259
260
  	}
  }
ff7a32673   Takashi Iwai   ALSA: hda - Don't...
261
  EXPORT_SYMBOL_HDA(snd_hda_detach_beep_device);