2008-07-18 18:20:52 +02:00
/*
* 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>
# include <linux/workqueue.h>
# include <sound/core.h>
# include "hda_beep.h"
2009-06-29 08:34:06 +02:00
# include "hda_local.h"
2008-07-18 18:20:52 +02:00
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 ;
2008-11-12 16:45:04 +01:00
if ( ! beep - > enabled )
return ;
2008-07-18 18:20:52 +02:00
/* generate tone */
snd_hda_codec_write_cache ( codec , beep - > nid , 0 ,
AC_VERB_SET_BEEP_CONTROL , beep - > tone ) ;
}
2009-05-19 12:50:04 +02:00
/* (non-standard) Linear beep tone calculation for IDT/STAC codecs
*
* The tone frequency of beep generator on IDT / STAC codecs is
* defined from the 8 bit tone parameter , in Hz ,
* freq = 48000 * ( 257 - tone ) / 1024
2009-07-08 23:57:46 -07:00
* that is from 12 kHz to 93.75 Hz in steps of 46.875 Hz
2009-05-19 12:50:04 +02:00
*/
static int beep_linear_tone ( struct hda_beep * beep , int hz )
{
2009-07-08 23:57:46 -07:00
if ( hz < = 0 )
return 0 ;
2009-05-19 12:50:04 +02:00
hz * = 1000 ; /* fixed point */
2009-07-08 23:57:46 -07:00
hz = hz - DIGBEEP_HZ_MIN
+ DIGBEEP_HZ_STEP / 2 ; /* round to nearest step */
2009-05-19 12:50:04 +02:00
if ( hz < 0 )
hz = 0 ; /* turn off PC beep*/
else if ( hz > = ( DIGBEEP_HZ_MAX - DIGBEEP_HZ_MIN ) )
2009-07-08 23:57:46 -07:00
hz = 1 ; /* max frequency */
2009-05-19 12:50:04 +02:00
else {
hz / = DIGBEEP_HZ_STEP ;
2009-07-08 23:57:46 -07:00
hz = 255 - hz ;
2009-05-19 12:50:04 +02:00
}
return hz ;
}
/* HD-audio standard beep tone parameter calculation
*
* The tone frequency in Hz is calculated as
* freq = 48000 / ( tone * 4 )
* from 47 Hz to 12 kHz
*/
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 ;
}
2008-07-18 18:20:52 +02:00
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 :
2009-05-19 12:50:04 +02:00
if ( beep - > linear_tone )
beep - > tone = beep_linear_tone ( beep , hz ) ;
else
beep - > tone = beep_standard_tone ( beep , hz ) ;
2008-07-18 18:20:52 +02:00
break ;
default :
return - 1 ;
}
/* schedule beep event */
schedule_work ( & beep - > beep_work ) ;
return 0 ;
}
2009-10-21 14:48:23 +02:00
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 */
snd_hda_codec_write_cache ( beep - > codec , beep - > nid , 0 ,
AC_VERB_SET_BEEP_CONTROL , 0 ) ;
}
static int snd_hda_do_attach ( struct hda_beep * beep )
2008-07-18 18:20:52 +02:00
{
struct input_dev * input_dev ;
2009-10-21 14:48:23 +02:00
struct hda_codec * codec = beep - > codec ;
2008-07-18 18:20:52 +02:00
int err ;
input_dev = input_allocate_device ( ) ;
2008-11-13 13:08:56 +01:00
if ( ! input_dev ) {
2009-10-21 14:48:23 +02:00
printk ( KERN_INFO " hda_beep: unable to allocate input device \n " ) ;
2008-11-13 13:08:56 +01:00
return - ENOMEM ;
}
2008-07-18 18:20:52 +02:00
/* 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 ) {
2008-07-29 12:08:16 +02:00
input_free_device ( input_dev ) ;
2009-10-21 14:48:23 +02:00
printk ( KERN_INFO " hda_beep: unable to register input device \n " ) ;
2008-07-18 18:20:52 +02:00
return err ;
}
2009-10-21 14:48:23 +02:00
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 ) ;
mutex_lock ( & beep - > mutex ) ;
2009-11-03 14:29:50 +01:00
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 ) ;
2009-10-21 14:48:23 +02:00
mutex_unlock ( & beep - > mutex ) ;
}
int snd_hda_enable_beep_device ( struct hda_codec * codec , int enable )
{
struct hda_beep * beep = codec - > beep ;
enable = ! ! enable ;
2009-11-03 14:29:50 +01:00
if ( beep = = NULL )
return 0 ;
if ( beep - > enabled ! = enable ) {
beep - > enabled = enable ;
if ( enable ) {
cancel_delayed_work ( & beep - > unregister_work ) ;
schedule_work ( & beep - > register_work ) ;
} else {
/* turn off beep */
snd_hda_codec_write_cache ( beep - > codec , beep - > nid , 0 ,
AC_VERB_SET_BEEP_CONTROL , 0 ) ;
schedule_delayed_work ( & beep - > unregister_work , HZ ) ;
}
2009-10-21 14:48:23 +02:00
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 " ) )
return 0 ; /* disabled explicitly */
2008-07-18 18:20:52 +02:00
2009-10-21 14:48:23 +02:00
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 ) ;
2008-07-18 18:20:52 +02:00
/* enable linear scale */
snd_hda_codec_write ( codec , nid , 0 ,
AC_VERB_SET_DIGI_CONVERT_2 , 0x01 ) ;
beep - > nid = nid ;
beep - > codec = codec ;
codec - > beep = beep ;
2009-10-21 14:48:23 +02:00
INIT_WORK ( & beep - > register_work , & snd_hda_do_register ) ;
2009-11-03 14:29:50 +01:00
INIT_DELAYED_WORK ( & beep - > unregister_work , & snd_hda_do_unregister ) ;
2008-07-18 18:20:52 +02:00
INIT_WORK ( & beep - > beep_work , & snd_hda_generate_beep ) ;
2009-10-21 14:48:23 +02:00
mutex_init ( & beep - > mutex ) ;
2008-07-18 18:20:52 +02:00
return 0 ;
}
2008-11-28 15:17:06 +01:00
EXPORT_SYMBOL_HDA ( snd_hda_attach_beep_device ) ;
2008-07-18 18:20:52 +02:00
void snd_hda_detach_beep_device ( struct hda_codec * codec )
{
struct hda_beep * beep = codec - > beep ;
if ( beep ) {
2009-10-21 14:48:23 +02:00
cancel_work_sync ( & beep - > register_work ) ;
if ( beep - > enabled )
snd_hda_do_detach ( beep ) ;
2009-02-06 16:48:10 +01:00
codec - > beep = NULL ;
2009-10-21 14:48:23 +02:00
kfree ( beep ) ;
2008-07-18 18:20:52 +02:00
}
}
2008-11-28 15:17:06 +01:00
EXPORT_SYMBOL_HDA ( snd_hda_detach_beep_device ) ;