2008-03-03 10:53:54 +01:00
/*
* PC - Speaker driver for Linux
*
* Copyright ( C ) 1997 - 2001 David Woodhouse
* Copyright ( C ) 2001 - 2008 Stas Sergeev
*/
# include <linux/init.h>
2011-07-15 13:13:37 -04:00
# include <linux/module.h>
2008-03-03 10:53:54 +01:00
# include <linux/platform_device.h>
# include <sound/core.h>
# include <sound/initval.h>
# include <sound/pcm.h>
# include <linux/input.h>
2008-03-18 09:03:03 +01:00
# include <linux/delay.h>
2008-03-03 10:53:54 +01:00
# include <asm/bitops.h>
# include "pcsp_input.h"
# include "pcsp.h"
MODULE_AUTHOR ( " Stas Sergeev <stsp@users.sourceforge.net> " ) ;
MODULE_DESCRIPTION ( " PC-Speaker driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_SUPPORTED_DEVICE ( " {{PC-Speaker, pcsp}} " ) ;
MODULE_ALIAS ( " platform:pcspkr " ) ;
static int index = SNDRV_DEFAULT_IDX1 ; /* Index 0-MAX */
static char * id = SNDRV_DEFAULT_STR1 ; /* ID for this card */
2011-12-15 13:49:36 +10:30
static bool enable = SNDRV_DEFAULT_ENABLE1 ; /* Enable this card */
static bool nopcm ; /* Disable PCM capability of the driver */
2008-03-03 10:53:54 +01:00
module_param ( index , int , 0444 ) ;
MODULE_PARM_DESC ( index , " Index value for pcsp soundcard. " ) ;
module_param ( id , charp , 0444 ) ;
MODULE_PARM_DESC ( id , " ID string for pcsp soundcard. " ) ;
module_param ( enable , bool , 0444 ) ;
2008-03-06 11:01:16 +01:00
MODULE_PARM_DESC ( enable , " Enable PC-Speaker sound. " ) ;
2009-11-01 11:13:19 +01:00
module_param ( nopcm , bool , 0444 ) ;
MODULE_PARM_DESC ( nopcm , " Disable PC-Speaker PCM sound. Only beeps remain. " ) ;
2008-03-03 10:53:54 +01:00
struct snd_pcsp pcsp_chip ;
2012-12-06 12:35:27 -05:00
static int snd_pcsp_create ( struct snd_card * card )
2008-03-03 10:53:54 +01:00
{
static struct snd_device_ops ops = { } ;
struct timespec tp ;
int err ;
int div , min_div , order ;
2013-10-29 15:15:20 +01:00
hrtimer_get_res ( CLOCK_MONOTONIC , & tp ) ;
2009-11-01 11:13:19 +01:00
if ( ! nopcm ) {
if ( tp . tv_sec | | tp . tv_nsec > PCSP_MAX_PERIOD_NS ) {
printk ( KERN_ERR " PCSP: Timer resolution is not sufficient "
" (%linS) \n " , tp . tv_nsec ) ;
printk ( KERN_ERR " PCSP: Make sure you have HPET and ACPI "
" enabled. \n " ) ;
printk ( KERN_ERR " PCSP: Turned into nopcm mode. \n " ) ;
nopcm = 1 ;
}
2008-03-03 10:53:54 +01:00
}
if ( loops_per_jiffy > = PCSP_MIN_LPJ & & tp . tv_nsec < = PCSP_MIN_PERIOD_NS )
min_div = MIN_DIV ;
else
min_div = MAX_DIV ;
# if PCSP_DEBUG
2009-02-05 15:51:50 +01:00
printk ( KERN_DEBUG " PCSP: lpj=%li, min_div=%i, res=%li \n " ,
2008-03-03 10:53:54 +01:00
loops_per_jiffy , min_div , tp . tv_nsec ) ;
# endif
div = MAX_DIV / min_div ;
order = fls ( div ) - 1 ;
pcsp_chip . max_treble = min ( order , PCSP_MAX_TREBLE ) ;
pcsp_chip . treble = min ( pcsp_chip . max_treble , PCSP_DEFAULT_TREBLE ) ;
pcsp_chip . playback_ptr = 0 ;
pcsp_chip . period_ptr = 0 ;
atomic_set ( & pcsp_chip . timer_active , 0 ) ;
pcsp_chip . enable = 1 ;
pcsp_chip . pcspkr = 1 ;
spin_lock_init ( & pcsp_chip . substream_lock ) ;
pcsp_chip . card = card ;
pcsp_chip . port = 0x61 ;
pcsp_chip . irq = - 1 ;
pcsp_chip . dma = - 1 ;
/* Register device */
err = snd_device_new ( card , SNDRV_DEV_LOWLEVEL , & pcsp_chip , & ops ) ;
if ( err < 0 )
return err ;
return 0 ;
}
2012-12-06 12:35:27 -05:00
static int snd_card_pcsp_probe ( int devnum , struct device * dev )
2008-03-03 10:53:54 +01:00
{
struct snd_card * card ;
int err ;
if ( devnum ! = 0 )
return - EINVAL ;
hrtimer_init ( & pcsp_chip . timer , CLOCK_MONOTONIC , HRTIMER_MODE_REL ) ;
pcsp_chip . timer . function = pcsp_do_timer ;
2014-01-29 12:59:08 +01:00
err = snd_card_new ( dev , index , id , THIS_MODULE , 0 , & card ) ;
2008-12-28 16:45:02 +01:00
if ( err < 0 )
return err ;
2008-03-03 10:53:54 +01:00
err = snd_pcsp_create ( card ) ;
if ( err < 0 ) {
snd_card_free ( card ) ;
return err ;
}
2009-11-01 11:13:19 +01:00
if ( ! nopcm ) {
err = snd_pcsp_new_pcm ( & pcsp_chip ) ;
if ( err < 0 ) {
snd_card_free ( card ) ;
return err ;
}
2008-03-03 10:53:54 +01:00
}
2009-11-01 11:13:19 +01:00
err = snd_pcsp_new_mixer ( & pcsp_chip , nopcm ) ;
2008-03-03 10:53:54 +01:00
if ( err < 0 ) {
snd_card_free ( card ) ;
return err ;
}
strcpy ( card - > driver , " PC-Speaker " ) ;
strcpy ( card - > shortname , " pcsp " ) ;
sprintf ( card - > longname , " Internal PC-Speaker at port 0x%x " ,
pcsp_chip . port ) ;
err = snd_card_register ( card ) ;
if ( err < 0 ) {
snd_card_free ( card ) ;
return err ;
}
return 0 ;
}
2012-12-06 12:35:27 -05:00
static int alsa_card_pcsp_init ( struct device * dev )
2008-03-03 10:53:54 +01:00
{
2008-03-06 11:01:16 +01:00
int err ;
err = snd_card_pcsp_probe ( 0 , dev ) ;
if ( err ) {
printk ( KERN_ERR " PC-Speaker initialization failed. \n " ) ;
return err ;
}
2008-03-03 10:53:54 +01:00
# ifdef CONFIG_DEBUG_PAGEALLOC
/* Well, CONFIG_DEBUG_PAGEALLOC makes the sound horrible. Lets alert */
2008-04-23 17:16:38 +02:00
printk ( KERN_WARNING " PCSP: CONFIG_DEBUG_PAGEALLOC is enabled, "
" which may make the sound noisy. \n " ) ;
2008-03-03 10:53:54 +01:00
# endif
return 0 ;
}
2012-12-06 12:35:27 -05:00
static void alsa_card_pcsp_exit ( struct snd_pcsp * chip )
2008-03-03 10:53:54 +01:00
{
snd_card_free ( chip - > card ) ;
}
2012-12-06 12:35:27 -05:00
static int pcsp_probe ( struct platform_device * dev )
2008-03-03 10:53:54 +01:00
{
int err ;
2008-03-06 11:01:16 +01:00
2008-03-03 10:53:54 +01:00
err = pcspkr_input_init ( & pcsp_chip . input_dev , & dev - > dev ) ;
if ( err < 0 )
return err ;
err = alsa_card_pcsp_init ( & dev - > dev ) ;
if ( err < 0 ) {
pcspkr_input_remove ( pcsp_chip . input_dev ) ;
return err ;
}
platform_set_drvdata ( dev , & pcsp_chip ) ;
return 0 ;
}
2012-12-06 12:35:27 -05:00
static int pcsp_remove ( struct platform_device * dev )
2008-03-03 10:53:54 +01:00
{
struct snd_pcsp * chip = platform_get_drvdata ( dev ) ;
pcspkr_input_remove ( chip - > input_dev ) ;
2013-11-14 15:45:12 +01:00
alsa_card_pcsp_exit ( chip ) ;
2008-03-03 10:53:54 +01:00
return 0 ;
}
static void pcsp_stop_beep ( struct snd_pcsp * chip )
{
2008-08-11 10:18:39 +02:00
pcsp_sync_stop ( chip ) ;
pcspkr_stop_sound ( ) ;
2008-03-03 10:53:54 +01:00
}
2012-08-09 15:47:15 +02:00
# ifdef CONFIG_PM_SLEEP
2012-07-02 11:22:40 +02:00
static int pcsp_suspend ( struct device * dev )
2008-03-03 10:53:54 +01:00
{
2012-07-02 11:22:40 +02:00
struct snd_pcsp * chip = dev_get_drvdata ( dev ) ;
2008-03-03 10:53:54 +01:00
pcsp_stop_beep ( chip ) ;
snd_pcm_suspend_all ( chip - > pcm ) ;
return 0 ;
}
2012-07-02 11:22:40 +02:00
static SIMPLE_DEV_PM_OPS ( pcsp_pm , pcsp_suspend , NULL ) ;
# define PCSP_PM_OPS &pcsp_pm
2008-05-02 09:54:31 +02:00
# else
2012-07-02 11:22:40 +02:00
# define PCSP_PM_OPS NULL
2012-08-09 15:47:15 +02:00
# endif /* CONFIG_PM_SLEEP */
2008-03-03 10:53:54 +01:00
static void pcsp_shutdown ( struct platform_device * dev )
{
struct snd_pcsp * chip = platform_get_drvdata ( dev ) ;
pcsp_stop_beep ( chip ) ;
}
static struct platform_driver pcsp_platform_driver = {
. driver = {
. name = " pcspkr " ,
. owner = THIS_MODULE ,
2012-07-02 11:22:40 +02:00
. pm = PCSP_PM_OPS ,
2008-03-03 10:53:54 +01:00
} ,
. probe = pcsp_probe ,
2012-12-06 12:35:27 -05:00
. remove = pcsp_remove ,
2008-03-03 10:53:54 +01:00
. shutdown = pcsp_shutdown ,
} ;
static int __init pcsp_init ( void )
{
2008-03-06 11:01:16 +01:00
if ( ! enable )
return - ENODEV ;
2008-03-03 10:53:54 +01:00
return platform_driver_register ( & pcsp_platform_driver ) ;
}
static void __exit pcsp_exit ( void )
{
platform_driver_unregister ( & pcsp_platform_driver ) ;
}
module_init ( pcsp_init ) ;
module_exit ( pcsp_exit ) ;