diff options
Diffstat (limited to 'sound/i2c')
-rw-r--r-- | sound/i2c/Makefile | 1 | ||||
-rw-r--r-- | sound/i2c/other/Makefile | 4 | ||||
-rw-r--r-- | sound/i2c/other/ak4114.c | 29 | ||||
-rw-r--r-- | sound/i2c/other/ak4117.c | 2 | ||||
-rw-r--r-- | sound/i2c/other/ak4xxx-adda.c | 110 | ||||
-rw-r--r-- | sound/i2c/other/pt2258.c | 233 |
6 files changed, 344 insertions, 35 deletions
diff --git a/sound/i2c/Makefile b/sound/i2c/Makefile index 816a2e7c88c..45902d48c89 100644 --- a/sound/i2c/Makefile +++ b/sound/i2c/Makefile @@ -16,3 +16,4 @@ obj-$(CONFIG_SND) += other/ # Toplevel Module Dependency obj-$(CONFIG_SND_INTERWAVE_STB) += snd-tea6330t.o snd-i2c.o obj-$(CONFIG_SND_ICE1712) += snd-cs8427.o snd-i2c.o +obj-$(CONFIG_SND_ICE1724) += snd-i2c.o diff --git a/sound/i2c/other/Makefile b/sound/i2c/other/Makefile index 2fe023ef00a..77a8a7c75dd 100644 --- a/sound/i2c/other/Makefile +++ b/sound/i2c/other/Makefile @@ -6,11 +6,11 @@ snd-ak4114-objs := ak4114.o snd-ak4117-objs := ak4117.o snd-ak4xxx-adda-objs := ak4xxx-adda.o +snd-pt2258-objs := pt2258.o snd-tea575x-tuner-objs := tea575x-tuner.o # Module Dependency obj-$(CONFIG_SND_PDAUDIOCF) += snd-ak4117.o obj-$(CONFIG_SND_ICE1712) += snd-ak4xxx-adda.o -obj-$(CONFIG_SND_ICE1724) += snd-ak4xxx-adda.o -obj-$(CONFIG_SND_ICE1724) += snd-ak4114.o +obj-$(CONFIG_SND_ICE1724) += snd-ak4114.o snd-ak4xxx-adda.o snd-pt2258.o obj-$(CONFIG_SND_FM801_TEA575X) += snd-tea575x-tuner.o diff --git a/sound/i2c/other/ak4114.c b/sound/i2c/other/ak4114.c index d2f2c5078e6..adbfd5884d0 100644 --- a/sound/i2c/other/ak4114.c +++ b/sound/i2c/other/ak4114.c @@ -42,8 +42,8 @@ static void reg_write(struct ak4114 *ak4114, unsigned char reg, unsigned char va ak4114->write(ak4114->private_data, reg, val); if (reg <= AK4114_REG_INT1_MASK) ak4114->regmap[reg] = val; - else if (reg >= AK4114_REG_RXCSB0 && reg <= AK4114_REG_TXCSB4) - ak4114->txcsb[reg-AK4114_REG_RXCSB0] = val; + else if (reg >= AK4114_REG_TXCSB0 && reg <= AK4114_REG_TXCSB4) + ak4114->txcsb[reg-AK4114_REG_TXCSB0] = val; } static inline unsigned char reg_read(struct ak4114 *ak4114, unsigned char reg) @@ -66,10 +66,8 @@ static void snd_ak4114_free(struct ak4114 *chip) { chip->init = 1; /* don't schedule new work */ mb(); - if (chip->workqueue != NULL) { - flush_workqueue(chip->workqueue); - destroy_workqueue(chip->workqueue); - } + cancel_delayed_work(&chip->work); + flush_scheduled_work(); kfree(chip); } @@ -82,7 +80,7 @@ static int snd_ak4114_dev_free(struct snd_device *device) int snd_ak4114_create(struct snd_card *card, ak4114_read_t *read, ak4114_write_t *write, - unsigned char pgm[7], unsigned char txcsb[5], + const unsigned char pgm[7], const unsigned char txcsb[5], void *private_data, struct ak4114 **r_ak4114) { struct ak4114 *chip; @@ -100,18 +98,13 @@ int snd_ak4114_create(struct snd_card *card, chip->read = read; chip->write = write; chip->private_data = private_data; + INIT_DELAYED_WORK(&chip->work, ak4114_stats); for (reg = 0; reg < 7; reg++) chip->regmap[reg] = pgm[reg]; for (reg = 0; reg < 5; reg++) chip->txcsb[reg] = txcsb[reg]; - chip->workqueue = create_workqueue("snd-ak4114"); - if (chip->workqueue == NULL) { - kfree(chip); - return -ENOMEM; - } - snd_ak4114_reinit(chip); chip->rcs0 = reg_read(chip, AK4114_REG_RCS0) & ~(AK4114_QINT | AK4114_CINT); @@ -134,7 +127,8 @@ void snd_ak4114_reg_write(struct ak4114 *chip, unsigned char reg, unsigned char if (reg <= AK4114_REG_INT1_MASK) reg_write(chip, reg, (chip->regmap[reg] & ~mask) | val); else if (reg >= AK4114_REG_TXCSB0 && reg <= AK4114_REG_TXCSB4) - reg_write(chip, reg, (chip->txcsb[reg] & ~mask) | val); + reg_write(chip, reg, + (chip->txcsb[reg-AK4114_REG_TXCSB0] & ~mask) | val); } void snd_ak4114_reinit(struct ak4114 *chip) @@ -143,7 +137,7 @@ void snd_ak4114_reinit(struct ak4114 *chip) chip->init = 1; mb(); - flush_workqueue(chip->workqueue); + flush_scheduled_work(); /* bring the chip to reset state and powerdown state */ reg_write(chip, AK4114_REG_PWRDN, old & ~(AK4114_RST|AK4114_PWN)); udelay(200); @@ -158,8 +152,7 @@ void snd_ak4114_reinit(struct ak4114 *chip) reg_write(chip, AK4114_REG_PWRDN, old | AK4114_RST | AK4114_PWN); /* bring up statistics / event queing */ chip->init = 0; - INIT_DELAYED_WORK(&chip->work, ak4114_stats); - queue_delayed_work(chip->workqueue, &chip->work, HZ / 10); + schedule_delayed_work(&chip->work, HZ / 10); } static unsigned int external_rate(unsigned char rcs1) @@ -568,7 +561,7 @@ static void ak4114_stats(struct work_struct *work) if (chip->init) return; snd_ak4114_check_rate_and_errors(chip, 0); - queue_delayed_work(chip->workqueue, &chip->work, HZ / 10); + schedule_delayed_work(&chip->work, HZ / 10); } EXPORT_SYMBOL(snd_ak4114_create); diff --git a/sound/i2c/other/ak4117.c b/sound/i2c/other/ak4117.c index 4e45952dd95..c022f29da2f 100644 --- a/sound/i2c/other/ak4117.c +++ b/sound/i2c/other/ak4117.c @@ -74,7 +74,7 @@ static int snd_ak4117_dev_free(struct snd_device *device) } int snd_ak4117_create(struct snd_card *card, ak4117_read_t *read, ak4117_write_t *write, - unsigned char pgm[5], void *private_data, struct ak4117 **r_ak4117) + const unsigned char pgm[5], void *private_data, struct ak4117 **r_ak4117) { struct ak4117 *chip; int err = 0; diff --git a/sound/i2c/other/ak4xxx-adda.c b/sound/i2c/other/ak4xxx-adda.c index 5da49e2eb35..8805110017a 100644 --- a/sound/i2c/other/ak4xxx-adda.c +++ b/sound/i2c/other/ak4xxx-adda.c @@ -140,7 +140,7 @@ EXPORT_SYMBOL(snd_akm4xxx_reset); * Used for AK4524 input/ouput attenuation, AK4528, and * AK5365 input attenuation */ -static unsigned char vol_cvt_datt[128] = { +static const unsigned char vol_cvt_datt[128] = { 0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x07, 0x07, 0x08, 0x08, 0x08, 0x09, 0x0a, @@ -162,17 +162,17 @@ static unsigned char vol_cvt_datt[128] = { /* * dB tables */ -static DECLARE_TLV_DB_SCALE(db_scale_vol_datt, -6350, 50, 1); -static DECLARE_TLV_DB_SCALE(db_scale_8bit, -12750, 50, 1); -static DECLARE_TLV_DB_SCALE(db_scale_7bit, -6350, 50, 1); -static DECLARE_TLV_DB_LINEAR(db_scale_linear, TLV_DB_GAIN_MUTE, 0); +static const DECLARE_TLV_DB_SCALE(db_scale_vol_datt, -6350, 50, 1); +static const DECLARE_TLV_DB_SCALE(db_scale_8bit, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(db_scale_7bit, -6350, 50, 1); +static const DECLARE_TLV_DB_LINEAR(db_scale_linear, TLV_DB_GAIN_MUTE, 0); /* * initialize all the ak4xxx chips */ void snd_akm4xxx_init(struct snd_akm4xxx *ak) { - static unsigned char inits_ak4524[] = { + static const unsigned char inits_ak4524[] = { 0x00, 0x07, /* 0: all power up */ 0x01, 0x00, /* 1: ADC/DAC reset */ 0x02, 0x60, /* 2: 24bit I2S */ @@ -184,7 +184,7 @@ void snd_akm4xxx_init(struct snd_akm4xxx *ak) 0x07, 0x00, /* 7: DAC right muted */ 0xff, 0xff }; - static unsigned char inits_ak4528[] = { + static const unsigned char inits_ak4528[] = { 0x00, 0x07, /* 0: all power up */ 0x01, 0x00, /* 1: ADC/DAC reset */ 0x02, 0x60, /* 2: 24bit I2S */ @@ -194,7 +194,7 @@ void snd_akm4xxx_init(struct snd_akm4xxx *ak) 0x05, 0x00, /* 5: ADC right muted */ 0xff, 0xff }; - static unsigned char inits_ak4529[] = { + static const unsigned char inits_ak4529[] = { 0x09, 0x01, /* 9: ATS=0, RSTN=1 */ 0x0a, 0x3f, /* A: all power up, no zero/overflow detection */ 0x00, 0x0c, /* 0: TDM=0, 24bit I2S, SMUTE=0 */ @@ -210,7 +210,7 @@ void snd_akm4xxx_init(struct snd_akm4xxx *ak) 0x08, 0x55, /* 8: deemphasis all off */ 0xff, 0xff }; - static unsigned char inits_ak4355[] = { + static const unsigned char inits_ak4355[] = { 0x01, 0x02, /* 1: reset and soft-mute */ 0x00, 0x06, /* 0: mode3(i2s), disable auto-clock detect, * disable DZF, sharp roll-off, RSTN#=0 */ @@ -227,7 +227,7 @@ void snd_akm4xxx_init(struct snd_akm4xxx *ak) 0x01, 0x01, /* 1: un-reset, unmute */ 0xff, 0xff }; - static unsigned char inits_ak4358[] = { + static const unsigned char inits_ak4358[] = { 0x01, 0x02, /* 1: reset and soft-mute */ 0x00, 0x06, /* 0: mode3(i2s), disable auto-clock detect, * disable DZF, sharp roll-off, RSTN#=0 */ @@ -246,7 +246,7 @@ void snd_akm4xxx_init(struct snd_akm4xxx *ak) 0x01, 0x01, /* 1: un-reset, unmute */ 0xff, 0xff }; - static unsigned char inits_ak4381[] = { + static const unsigned char inits_ak4381[] = { 0x00, 0x0c, /* 0: mode3(i2s), disable auto-clock detect */ 0x01, 0x02, /* 1: de-emphasis off, normal speed, * sharp roll-off, DZF off */ @@ -259,7 +259,8 @@ void snd_akm4xxx_init(struct snd_akm4xxx *ak) }; int chip, num_chips; - unsigned char *ptr, reg, data, *inits; + const unsigned char *ptr, *inits; + unsigned char reg, data; memset(ak->images, 0, sizeof(ak->images)); memset(ak->volumes, 0, sizeof(ak->volumes)); @@ -513,6 +514,66 @@ static int ak4xxx_switch_put(struct snd_kcontrol *kcontrol, return change; } +#define AK5365_NUM_INPUTS 5 + +static int ak4xxx_capture_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol); + int mixer_ch = AK_GET_SHIFT(kcontrol->private_value); + const char **input_names; + int num_names, idx; + + input_names = ak->adc_info[mixer_ch].input_names; + + num_names = 0; + while (num_names < AK5365_NUM_INPUTS && input_names[num_names]) + ++num_names; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = num_names; + idx = uinfo->value.enumerated.item; + if (idx >= num_names) + return -EINVAL; + strncpy(uinfo->value.enumerated.name, input_names[idx], + sizeof(uinfo->value.enumerated.name)); + return 0; +} + +static int ak4xxx_capture_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol); + int chip = AK_GET_CHIP(kcontrol->private_value); + int addr = AK_GET_ADDR(kcontrol->private_value); + int mask = AK_GET_MASK(kcontrol->private_value); + unsigned char val; + + val = snd_akm4xxx_get(ak, chip, addr) & mask; + ucontrol->value.enumerated.item[0] = val; + return 0; +} + +static int ak4xxx_capture_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_akm4xxx *ak = snd_kcontrol_chip(kcontrol); + int chip = AK_GET_CHIP(kcontrol->private_value); + int addr = AK_GET_ADDR(kcontrol->private_value); + int mask = AK_GET_MASK(kcontrol->private_value); + unsigned char oval, val; + + oval = snd_akm4xxx_get(ak, chip, addr); + val = oval & ~mask; + val |= ucontrol->value.enumerated.item[0] & mask; + if (val != oval) { + snd_akm4xxx_write(ak, chip, addr, val); + return 1; + } + return 0; +} + /* * build AK4xxx controls */ @@ -647,9 +708,10 @@ static int build_adc_controls(struct snd_akm4xxx *ak) if (ak->type == SND_AK5365 && (idx % 2) == 0) { if (! ak->adc_info || - ! ak->adc_info[mixer_ch].switch_name) + ! ak->adc_info[mixer_ch].switch_name) { knew.name = "Capture Switch"; - else + knew.index = mixer_ch + ak->idx_offset * 2; + } else knew.name = ak->adc_info[mixer_ch].switch_name; knew.info = ak4xxx_switch_info; knew.get = ak4xxx_switch_get; @@ -662,6 +724,26 @@ static int build_adc_controls(struct snd_akm4xxx *ak) err = snd_ctl_add(ak->card, snd_ctl_new1(&knew, ak)); if (err < 0) return err; + + memset(&knew, 0, sizeof(knew)); + knew.name = ak->adc_info[mixer_ch].selector_name; + if (!knew.name) { + knew.name = "Capture Channel"; + knew.index = mixer_ch + ak->idx_offset * 2; + } + + knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + knew.info = ak4xxx_capture_source_info; + knew.get = ak4xxx_capture_source_get; + knew.put = ak4xxx_capture_source_put; + knew.access = 0; + /* input selector control: reg. 1, bits 0-2. + * mis-use 'shift' to pass mixer_ch */ + knew.private_value + = AK_COMPOSE(idx/2, 1, mixer_ch, 0x07); + err = snd_ctl_add(ak->card, snd_ctl_new1(&knew, ak)); + if (err < 0) + return err; } idx += num_stereo; diff --git a/sound/i2c/other/pt2258.c b/sound/i2c/other/pt2258.c new file mode 100644 index 00000000000..e91cc3b44de --- /dev/null +++ b/sound/i2c/other/pt2258.c @@ -0,0 +1,233 @@ +/* + * ALSA Driver for the PT2258 volume controller. + * + * Copyright (c) 2006 Jochen Voss <voss@seehuhn.de> + * + * This program 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 program 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 <sound/driver.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/tlv.h> +#include <sound/i2c.h> +#include <sound/pt2258.h> + +MODULE_AUTHOR("Jochen Voss <voss@seehuhn.de>"); +MODULE_DESCRIPTION("PT2258 volume controller (Princeton Technology Corp.)"); +MODULE_LICENSE("GPL"); + +#define PT2258_CMD_RESET 0xc0 +#define PT2258_CMD_UNMUTE 0xf8 +#define PT2258_CMD_MUTE 0xf9 + +static const unsigned char pt2258_channel_code[12] = { + 0x80, 0x90, /* channel 1: -10dB, -1dB */ + 0x40, 0x50, /* channel 2: -10dB, -1dB */ + 0x00, 0x10, /* channel 3: -10dB, -1dB */ + 0x20, 0x30, /* channel 4: -10dB, -1dB */ + 0x60, 0x70, /* channel 5: -10dB, -1dB */ + 0xa0, 0xb0 /* channel 6: -10dB, -1dB */ +}; + +int snd_pt2258_reset(struct snd_pt2258 *pt) +{ + unsigned char bytes[2]; + int i; + + /* reset chip */ + bytes[0] = PT2258_CMD_RESET; + snd_i2c_lock(pt->i2c_bus); + if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 1) != 1) + goto __error; + snd_i2c_unlock(pt->i2c_bus); + + /* mute all channels */ + pt->mute = 1; + bytes[0] = PT2258_CMD_MUTE; + snd_i2c_lock(pt->i2c_bus); + if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 1) != 1) + goto __error; + snd_i2c_unlock(pt->i2c_bus); + + /* set all channels to 0dB */ + for (i = 0; i < 6; ++i) + pt->volume[i] = 0; + bytes[0] = 0xd0; + bytes[1] = 0xe0; + snd_i2c_lock(pt->i2c_bus); + if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 2) != 2) + goto __error; + snd_i2c_unlock(pt->i2c_bus); + + return 0; + + __error: + snd_i2c_unlock(pt->i2c_bus); + snd_printk(KERN_ERR "PT2258 reset failed\n"); + return -EIO; +} + +static int pt2258_stereo_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 79; + return 0; +} + +static int pt2258_stereo_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pt2258 *pt = kcontrol->private_data; + int base = kcontrol->private_value; + + /* chip does not support register reads */ + ucontrol->value.integer.value[0] = 79 - pt->volume[base]; + ucontrol->value.integer.value[1] = 79 - pt->volume[base + 1]; + return 0; +} + +static int pt2258_stereo_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pt2258 *pt = kcontrol->private_data; + int base = kcontrol->private_value; + unsigned char bytes[2]; + int val0, val1; + + val0 = 79 - ucontrol->value.integer.value[0]; + val1 = 79 - ucontrol->value.integer.value[1]; + if (val0 == pt->volume[base] && val1 == pt->volume[base + 1]) + return 0; + + pt->volume[base] = val0; + bytes[0] = pt2258_channel_code[2 * base] | (val0 / 10); + bytes[1] = pt2258_channel_code[2 * base + 1] | (val0 % 10); + snd_i2c_lock(pt->i2c_bus); + if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 2) != 2) + goto __error; + snd_i2c_unlock(pt->i2c_bus); + + pt->volume[base + 1] = val1; + bytes[0] = pt2258_channel_code[2 * base + 2] | (val1 / 10); + bytes[1] = pt2258_channel_code[2 * base + 3] | (val1 % 10); + snd_i2c_lock(pt->i2c_bus); + if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 2) != 2) + goto __error; + snd_i2c_unlock(pt->i2c_bus); + + return 1; + + __error: + snd_i2c_unlock(pt->i2c_bus); + snd_printk(KERN_ERR "PT2258 access failed\n"); + return -EIO; +} + +static int pt2258_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int pt2258_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pt2258 *pt = kcontrol->private_data; + + ucontrol->value.integer.value[0] = !pt->mute; + return 0; +} + +static int pt2258_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pt2258 *pt = kcontrol->private_data; + unsigned char bytes[2]; + int val; + + val = !ucontrol->value.integer.value[0]; + if (pt->mute == val) + return 0; + + pt->mute = val; + bytes[0] = val ? PT2258_CMD_MUTE : PT2258_CMD_UNMUTE; + snd_i2c_lock(pt->i2c_bus); + if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 1) != 1) + goto __error; + snd_i2c_unlock(pt->i2c_bus); + + return 1; + + __error: + snd_i2c_unlock(pt->i2c_bus); + snd_printk(KERN_ERR "PT2258 access failed 2\n"); + return -EIO; +} + +static const DECLARE_TLV_DB_SCALE(pt2258_db_scale, -7900, 100, 0); + +int snd_pt2258_build_controls(struct snd_pt2258 *pt) +{ + struct snd_kcontrol_new knew; + char *names[3] = { + "Mic Loopback Playback Volume", + "Line Loopback Playback Volume", + "CD Loopback Playback Volume" + }; + int i, err; + + for (i = 0; i < 3; ++i) { + memset(&knew, 0, sizeof(knew)); + knew.name = names[i]; + knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + knew.count = 1; + knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ; + knew.private_value = 2 * i; + knew.info = pt2258_stereo_volume_info; + knew.get = pt2258_stereo_volume_get; + knew.put = pt2258_stereo_volume_put; + knew.tlv.p = pt2258_db_scale; + + err = snd_ctl_add(pt->card, snd_ctl_new1(&knew, pt)); + if (err < 0) + return err; + } + + memset(&knew, 0, sizeof(knew)); + knew.name = "Loopback Switch"; + knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + knew.info = pt2258_switch_info; + knew.get = pt2258_switch_get; + knew.put = pt2258_switch_put; + knew.access = 0; + err = snd_ctl_add(pt->card, snd_ctl_new1(&knew, pt)); + if (err < 0) + return err; + + return 0; +} + +EXPORT_SYMBOL(snd_pt2258_reset); +EXPORT_SYMBOL(snd_pt2258_build_controls); |