1
0
mirror of git://projects.qi-hardware.com/openwrt-xburst.git synced 2024-11-25 16:34:04 +02:00
openwrt-xburst/target/linux/xburst/files-2.6.31/sound/soc/codecs/jzcodec.c

726 lines
18 KiB
C
Raw Normal View History

2009-08-20 12:38:17 +03:00
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/ac97_codec.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include "../jz4740/jz4740-pcm.h"
#include "jzcodec.h"
#define AUDIO_NAME "jzcodec"
#define JZCODEC_VERSION "1.0"
/*
* Debug
*/
#define JZCODEC_DEBUG 0
#ifdef JZCODEC_DEBUG
#define dbg(format, arg...) \
printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg)
#else
#define dbg(format, arg...) do {} while (0)
#endif
#define err(format, arg...) \
printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg)
#define info(format, arg...) \
printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg)
#define warn(format, arg...) \
printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg)
struct snd_soc_codec_device soc_codec_dev_jzcodec;
/* codec private data */
struct jzcodec_priv {
unsigned int sysclk;
};
/*
* jzcodec register cache
*/
static u32 jzcodec_reg[JZCODEC_CACHEREGNUM / 2];
/*
* codec register is 16 bits width in ALSA, so we define array to store 16 bits configure paras
*/
static u16 jzcodec_reg_LH[JZCODEC_CACHEREGNUM];
/*
* read jzcodec register cache
*/
static inline unsigned int jzcodec_read_reg_cache(struct snd_soc_codec *codec,
unsigned int reg)
{
u16 *cache = codec->reg_cache;
if (reg >= JZCODEC_CACHEREGNUM)
return -1;
return cache[reg];
}
/*
* write jzcodec register cache
*/
static inline void jzcodec_write_reg_cache(struct snd_soc_codec *codec,
unsigned int reg, u16 value)
{
u16 *cache = codec->reg_cache;
u32 reg_val;
if (reg >= JZCODEC_CACHEREGNUM) {
return;
}
cache[reg] = value;
/* update internal codec register value */
switch (reg) {
case 0:
case 1:
reg_val = cache[0] & 0xffff;
reg_val = reg_val | (cache[1] << 16);
jzcodec_reg[0] = reg_val;
break;
case 2:
case 3:
reg_val = cache[2] & 0xffff;
reg_val = reg_val | (cache[3] << 16);
jzcodec_reg[1] = reg_val;
break;
}
}
/*
* write to the jzcodec register space
*/
static int jzcodec_write(struct snd_soc_codec *codec, unsigned int reg,
unsigned int value)
{
jzcodec_write_reg_cache(codec, reg, value);
if(codec->hw_write)
codec->hw_write(&value, NULL, reg);
return 0;
}
static int jzcodec_reset(struct snd_soc_codec *codec)
{
u16 val;
val = jzcodec_read_reg_cache(codec, ICODEC_1_LOW);
val = val | 0x1;
jzcodec_write(codec, ICODEC_1_LOW, val);
mdelay(1);
val = jzcodec_read_reg_cache(codec, ICODEC_1_LOW);
val = val & ~0x1;
jzcodec_write(codec, ICODEC_1_LOW, val);
mdelay(1);
return 0;
}
static const struct snd_kcontrol_new jzcodec_snd_controls[] = {
//SOC_DOUBLE_R("Master Playback Volume", 1, 1, 0, 3, 0),
SOC_DOUBLE_R("Master Playback Volume", ICODEC_2_LOW, ICODEC_2_LOW, 0, 3, 0),
//SOC_DOUBLE_R("MICBG", ICODEC_2_LOW, ICODEC_2_LOW, 4, 3, 0),
//SOC_DOUBLE_R("Line", 2, 2, 0, 31, 0),
SOC_DOUBLE_R("Line", ICODEC_2_HIGH, ICODEC_2_HIGH, 0, 31, 0),
};
/* add non dapm controls */
static int jzcodec_add_controls(struct snd_soc_codec *codec)
{
int err, i;
for (i = 0; i < ARRAY_SIZE(jzcodec_snd_controls); i++) {
if ((err = snd_ctl_add(codec->card,
snd_soc_cnew(&jzcodec_snd_controls[i], codec, NULL))) < 0)
return err;
}
return 0;
}
static const struct snd_soc_dapm_widget jzcodec_dapm_widgets[] = {
SND_SOC_DAPM_OUTPUT("LOUT"),
SND_SOC_DAPM_OUTPUT("LHPOUT"),
SND_SOC_DAPM_OUTPUT("ROUT"),
SND_SOC_DAPM_OUTPUT("RHPOUT"),
SND_SOC_DAPM_INPUT("MICIN"),
SND_SOC_DAPM_INPUT("RLINEIN"),
SND_SOC_DAPM_INPUT("LLINEIN"),
};
static const char *intercon[][3] = {
/* output mixer */
{"Output Mixer", "Line Bypass Switch", "Line Input"},
{"Output Mixer", "HiFi Playback Switch", "DAC"},
{"Output Mixer", "Mic Sidetone Switch", "Mic Bias"},
/* outputs */
{"RHPOUT", NULL, "Output Mixer"},
{"ROUT", NULL, "Output Mixer"},
{"LHPOUT", NULL, "Output Mixer"},
{"LOUT", NULL, "Output Mixer"},
/* input mux */
{"Input Mux", "Line In", "Line Input"},
{"Input Mux", "Mic", "Mic Bias"},
{"ADC", NULL, "Input Mux"},
/* inputs */
{"Line Input", NULL, "LLINEIN"},
{"Line Input", NULL, "RLINEIN"},
{"Mic Bias", NULL, "MICIN"},
/* terminator */
{NULL, NULL, NULL},
};
static int jzcodec_add_widgets(struct snd_soc_codec *codec)
{
int i,cnt;
cnt = ARRAY_SIZE(jzcodec_dapm_widgets);
for(i = 0; i < ARRAY_SIZE(jzcodec_dapm_widgets); i++) {
snd_soc_dapm_new_control(codec, &jzcodec_dapm_widgets[i]);
}
#if 1
/* set up audio path interconnects */
for(i = 0; intercon[i][0] != NULL; i++) {
snd_soc_dapm_connect_input(codec, intercon[i][0],
intercon[i][1], intercon[i][2]);
}
#endif
snd_soc_dapm_new_widgets(codec);
return 0;
}
static int jzcodec_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
struct snd_soc_codec *codec = socdev->codec;
u16 reg_val = jzcodec_read_reg_cache(codec, ICODEC_2_LOW);
#if 0
/* bit size. codec side */
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
break;
}
#endif
/* sample rate */
reg_val = reg_val & ~(0xf << 8);
switch (params_rate(params)) {
case 8000:
reg_val |= (0x0 << 8);
break;
case 11025:
reg_val |= (0x1 << 8);
break;
case 12000:
reg_val |= (0x2 << 8);
break;
case 16000:
reg_val |= (0x3 << 8);
break;
case 22050:
reg_val |= (0x4 << 8);
break;
case 24000:
reg_val |= (0x5 << 8);
break;
case 32000:
reg_val |= (0x6 << 8);
break;
case 44100:
reg_val |= (0x7 << 8);
break;
case 48000:
reg_val |= (0x8 << 8);
break;
default:
printk(" invalid rate :0x%08x\n",params_rate(params));
}
jzcodec_write(codec, ICODEC_2_LOW, reg_val);
return 0;
}
static int jzcodec_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
int ret = 0;
u16 val;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
struct snd_soc_codec *codec = socdev->codec;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
//case SNDRV_PCM_TRIGGER_RESUME:
//case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
val = 0x7302;
jzcodec_write(codec, ICODEC_1_LOW, val);
val = 0x0003;
jzcodec_write(codec, ICODEC_1_HIGH, val);
mdelay(2);
val = 0x6000;
jzcodec_write(codec, ICODEC_1_LOW, val);
val = 0x0300;
jzcodec_write(codec, ICODEC_1_HIGH, val);
mdelay(2);
val = 0x2000;
jzcodec_write(codec, ICODEC_1_LOW, val);
val = 0x0300;
jzcodec_write(codec, ICODEC_1_HIGH, val);
} else {
val = 0x4300;
jzcodec_write(codec, ICODEC_1_LOW, val);
val = 0x1402;
jzcodec_write(codec, ICODEC_1_HIGH, val);
}
break;
case SNDRV_PCM_TRIGGER_STOP:
//case SNDRV_PCM_TRIGGER_SUSPEND:
//case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
val = 0x3300;
jzcodec_write(codec, ICODEC_1_LOW, val);
val = 0x0003;
jzcodec_write(codec, ICODEC_1_HIGH, val);
} else {
val = 0x3300;
jzcodec_write(codec, ICODEC_1_LOW, val);
val = 0x0003;
jzcodec_write(codec, ICODEC_1_HIGH, val);
}
break;
default:
ret = -EINVAL;
}
return ret;
}
static int jzcodec_pcm_prepare(struct snd_pcm_substream *substream)
{
/*struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
struct snd_soc_codec *codec = socdev->codec; */
return 0;
}
static void jzcodec_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
struct snd_soc_codec *codec = socdev->codec;
/* deactivate */
if (!codec->active) {
udelay(50);
}
}
static int jzcodec_mute(struct snd_soc_dai *dai, int mute)
{
struct snd_soc_codec *codec = dai->codec;
u16 reg_val = jzcodec_read_reg_cache(codec, ICODEC_1_LOW);
if (mute != 0)
mute = 1;
if (mute)
reg_val = reg_val | (0x1 << 14);
else
reg_val = reg_val & ~(0x1 << 14);
jzcodec_write(codec, ICODEC_1_LOW, reg_val);
return 0;
}
static int jzcodec_set_dai_sysclk(struct snd_soc_dai *codec_dai,
int clk_id, unsigned int freq, int dir)
{
struct snd_soc_codec *codec = codec_dai->codec;
struct jzcodec_priv *jzcodec = codec->private_data;
jzcodec->sysclk = freq;
return 0;
}
/*
* Set's ADC and Voice DAC format. called by pavo_hw_params() in pavo.c
*/
static int jzcodec_set_dai_fmt(struct snd_soc_dai *codec_dai,
unsigned int fmt)
{
struct snd_soc_codec *codec = codec_dai->codec;
/* set master/slave audio interface. codec side */
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
/* set master mode for codec */
break;
case SND_SOC_DAIFMT_CBS_CFS:
/* set slave mode for codec */
break;
default:
return -EINVAL;
}
/* interface format . set some parameter for codec side */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
/* set I2S mode for codec */
break;
case SND_SOC_DAIFMT_RIGHT_J:
/* set right J mode */
break;
case SND_SOC_DAIFMT_LEFT_J:
/* set left J mode */
break;
case SND_SOC_DAIFMT_DSP_A:
/* set dsp A mode */
break;
case SND_SOC_DAIFMT_DSP_B:
/* set dsp B mode */
break;
default:
return -EINVAL;
}
/* clock inversion. codec side */
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
break;
case SND_SOC_DAIFMT_IB_IF:
break;
case SND_SOC_DAIFMT_IB_NF:
break;
case SND_SOC_DAIFMT_NB_IF:
break;
default:
return -EINVAL;
}
/* jzcodec_write(codec, 0, val); */
return 0;
}
#if 1
static int jzcodec_dapm_event(struct snd_soc_codec *codec, int event)
{
/* u16 reg_val; */
switch (event) {
case SNDRV_CTL_POWER_D0: /* full On */
/* vref/mid, osc on, dac unmute */
/* u16 reg_val = jzcodec_read_reg_cache(codec, ICODEC_1_LOW); */
/* jzcodec_write(codec, 0, val); */
break;
case SNDRV_CTL_POWER_D1: /* partial On */
case SNDRV_CTL_POWER_D2: /* partial On */
break;
case SNDRV_CTL_POWER_D3hot: /* Off, with power */
/* everything off except vref/vmid, */
/*reg_val = 0x0800;
jzcodec_write_reg_cache(codec, ICODEC_1_LOW, reg_val);
reg_val = 0x0017;
jzcodec_write_reg_cache(codec, ICODEC_1_HIGH, reg_val);
REG_ICDC_CDCCR1 = jzcodec_reg[0];
mdelay(2);
reg_val = 0x2102;
jzcodec_write_reg_cache(codec, ICODEC_1_LOW, reg_val);
reg_val = 0x001f;
jzcodec_write_reg_cache(codec, ICODEC_1_HIGH, reg_val);
REG_ICDC_CDCCR1 = jzcodec_reg[0];
mdelay(2);
reg_val = 0x3302;
jzcodec_write_reg_cache(codec, ICODEC_1_LOW, reg_val);
reg_val = 0x0003;
jzcodec_write_reg_cache(codec, ICODEC_1_HIGH, reg_val);
REG_ICDC_CDCCR1 = jzcodec_reg[0];*/
break;
case SNDRV_CTL_POWER_D3cold: /* Off, without power */
/* everything off, dac mute, inactive */
/*reg_val = 0x2302;
jzcodec_write(codec, ICODEC_1_LOW, reg_val);
reg_val = 0x001b;
jzcodec_write(codec, ICODEC_1_HIGH, reg_val);
mdelay(1);
reg_val = 0x2102;
jzcodec_write(codec, ICODEC_1_LOW, reg_val);
reg_val = 0x001b;
jzcodec_write(codec, ICODEC_1_HIGH, reg_val);*/
break;
}
//codec->dapm_state = event;
return 0;
}
#endif
#define JZCODEC_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
SNDRV_PCM_RATE_48000)
#define JZCODEC_FORMATS (SNDRV_PCM_FORMAT_S8 | SNDRV_PCM_FMTBIT_S16_LE)
struct snd_soc_dai jzcodec_dai = {
.name = "JZCODEC",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = JZCODEC_RATES,
.formats = JZCODEC_FORMATS,},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = JZCODEC_RATES,
.formats = JZCODEC_FORMATS,},
.ops = {
.trigger = jzcodec_pcm_trigger,
.prepare = jzcodec_pcm_prepare,
.hw_params = jzcodec_hw_params,
.shutdown = jzcodec_shutdown,
},
.dai_ops = {
.digital_mute = jzcodec_mute,
.set_sysclk = jzcodec_set_dai_sysclk,
.set_fmt = jzcodec_set_dai_fmt,
}
};
EXPORT_SYMBOL_GPL(jzcodec_dai);
#ifdef CONFIG_PM
static u16 jzcodec_reg_pm[JZCODEC_CACHEREGNUM];
static int jzcodec_suspend(struct platform_device *pdev, pm_message_t state)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec = socdev->codec;
jzcodec_reg_pm[ICODEC_1_LOW] = jzcodec_read_reg_cache(codec, ICODEC_1_LOW);
jzcodec_reg_pm[ICODEC_1_HIGH] = jzcodec_read_reg_cache(codec, ICODEC_1_HIGH);
jzcodec_reg_pm[ICODEC_2_LOW] = jzcodec_read_reg_cache(codec, ICODEC_2_LOW);
jzcodec_reg_pm[ICODEC_2_HIGH] = jzcodec_read_reg_cache(codec, ICODEC_2_HIGH);
jzcodec_dapm_event(codec, SNDRV_CTL_POWER_D3cold);
return 0;
}
static int jzcodec_resume(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec = socdev->codec;
u16 reg_val;
jzcodec_dapm_event(codec, SNDRV_CTL_POWER_D3hot);
reg_val = jzcodec_reg_pm[ICODEC_1_LOW];
jzcodec_write(codec, ICODEC_1_LOW, reg_val);
reg_val = jzcodec_reg_pm[ICODEC_1_HIGH];
jzcodec_write(codec, ICODEC_1_HIGH, reg_val);
reg_val = jzcodec_reg_pm[ICODEC_2_LOW];
jzcodec_write(codec, ICODEC_2_LOW, reg_val);
reg_val = jzcodec_reg_pm[ICODEC_2_HIGH];
jzcodec_write(codec, ICODEC_2_HIGH, reg_val);
jzcodec_dapm_event(codec, codec->suspend_dapm_state);
return 0;
}
#else
#define jzcodec_suspend NULL
#define jzcodec_resume NULL
#endif
/*
* initialise the JZCODEC driver
* register the mixer and dsp interfaces with the kernel
*/
static int jzcodec_init(struct snd_soc_device *socdev)
{
struct snd_soc_codec *codec = socdev->codec;
int reg, ret = 0;
u16 reg_val;
for (reg = 0; reg < JZCODEC_CACHEREGNUM / 2; reg++) {
switch (reg) {
case 0:
jzcodec_reg[reg] = REG_ICDC_CDCCR1;
jzcodec_reg_LH[ICODEC_1_LOW] = jzcodec_reg[reg] & 0xffff;
jzcodec_reg_LH[ICODEC_1_HIGH] = (jzcodec_reg[reg] & 0xffff0000) >> 16;
break;
case 1:
jzcodec_reg[reg] = REG_ICDC_CDCCR2;
jzcodec_reg_LH[ICODEC_2_LOW] = jzcodec_reg[reg] & 0xffff;
jzcodec_reg_LH[ICODEC_2_HIGH] = (jzcodec_reg[reg] & 0xffff0000) >> 16;
break;
}
}
codec->name = "JZCODEC";
codec->owner = THIS_MODULE;
codec->read = jzcodec_read_reg_cache;
codec->write = jzcodec_write;
//codec->dapm_event = jzcodec_dapm_event;
codec->dai = &jzcodec_dai;
codec->num_dai = 1;
codec->reg_cache_size = sizeof(jzcodec_reg_LH);
codec->reg_cache = kmemdup(jzcodec_reg_LH, sizeof(jzcodec_reg_LH), GFP_KERNEL);
if (codec->reg_cache == NULL)
return -ENOMEM;
jzcodec_reset(codec);
/* register pcms */
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
if (ret < 0) {
printk(KERN_ERR "jzcodec: failed to create pcms\n");
goto pcm_err;
}
/* power on device */
jzcodec_dapm_event(codec, SNDRV_CTL_POWER_D3hot);
/* clear suspend bit of jz4740 internal codec */
reg_val = jzcodec_read_reg_cache(codec, ICODEC_1_LOW);
reg_val = reg_val & ~(0x2);
jzcodec_write(codec, ICODEC_1_LOW, reg_val);
/* set vol bits */
reg_val = jzcodec_read_reg_cache(codec, ICODEC_2_LOW);
reg_val = reg_val | 0x3;
jzcodec_write(codec, ICODEC_2_LOW, reg_val);
/* set line in capture gain bits */
reg_val = jzcodec_read_reg_cache(codec, ICODEC_2_HIGH);
reg_val = reg_val | 0x1f;
jzcodec_write(codec, ICODEC_2_HIGH, reg_val);
/* set mic boost gain bits */
reg_val = jzcodec_read_reg_cache(codec, ICODEC_2_LOW);
reg_val = reg_val | (0x3 << 4);
jzcodec_write(codec, ICODEC_2_LOW, reg_val);
mdelay(5);
reg_val = 0x3300;
jzcodec_write(codec, ICODEC_1_LOW, reg_val);
reg_val = 0x0003;
jzcodec_write(codec, ICODEC_1_HIGH, reg_val);
jzcodec_add_controls(codec);
jzcodec_add_widgets(codec);
ret = snd_soc_register_card(socdev);
if (ret < 0) {
printk(KERN_ERR "jzcodec: failed to register card\n");
goto card_err;
}
return ret;
card_err:
snd_soc_free_pcms(socdev);
snd_soc_dapm_free(socdev);
pcm_err:
kfree(codec->reg_cache);
return ret;
}
static struct snd_soc_device *jzcodec_socdev;
static int write_codec_reg(u16 * add, char * name, int reg)
{
switch (reg) {
case 0:
case 1:
REG_ICDC_CDCCR1 = jzcodec_reg[0];
break;
case 2:
case 3:
REG_ICDC_CDCCR2 = jzcodec_reg[1];
break;
}
return 0;
}
static int jzcodec_probe(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec;
struct jzcodec_priv *jzcodec;
int ret = 0;
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
if (codec == NULL)
return -ENOMEM;
jzcodec = kzalloc(sizeof(struct jzcodec_priv), GFP_KERNEL);
if (jzcodec == NULL) {
kfree(codec);
return -ENOMEM;
}
codec->private_data = jzcodec;
socdev->codec = codec;
mutex_init(&codec->mutex);
INIT_LIST_HEAD(&codec->dapm_widgets);
INIT_LIST_HEAD(&codec->dapm_paths);
jzcodec_socdev = socdev;
/* Add other interfaces here ,no I2C connection */
codec->hw_write = (hw_write_t)write_codec_reg;
ret = jzcodec_init(jzcodec_socdev);
if (ret < 0) {
codec = jzcodec_socdev->codec;
err("failed to initialise jzcodec\n");
kfree(codec);
}
return ret;
}
/* power down chip */
static int jzcodec_remove(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec = socdev->codec;
if (codec->control_data)
jzcodec_dapm_event(codec, SNDRV_CTL_POWER_D3cold);
snd_soc_free_pcms(socdev);
snd_soc_dapm_free(socdev);
kfree(codec->private_data);
kfree(codec);
return 0;
}
struct snd_soc_codec_device soc_codec_dev_jzcodec = {
.probe = jzcodec_probe,
.remove = jzcodec_remove,
.suspend = jzcodec_suspend,
.resume = jzcodec_resume,
};
EXPORT_SYMBOL_GPL(soc_codec_dev_jzcodec);
MODULE_DESCRIPTION("ASoC JZCODEC driver");
MODULE_AUTHOR("Richard");
MODULE_LICENSE("GPL");