1 /* 2 * QEMU PC speaker emulation 3 * 4 * Copyright (c) 2006 Joachim Henke 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to deal 8 * in the Software without restriction, including without limitation the rights 9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 * copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in 14 * all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 * THE SOFTWARE. 23 */ 24 25 #include "qemu/osdep.h" 26 #include "hw/isa/isa.h" 27 #include "hw/audio/soundhw.h" 28 #include "audio/audio.h" 29 #include "qemu/module.h" 30 #include "qemu/timer.h" 31 #include "qemu/error-report.h" 32 #include "hw/timer/i8254.h" 33 #include "migration/vmstate.h" 34 #include "hw/audio/pcspk.h" 35 #include "qapi/error.h" 36 #include "qom/object.h" 37 38 #define PCSPK_BUF_LEN 1792 39 #define PCSPK_SAMPLE_RATE 32000 40 #define PCSPK_MAX_FREQ (PCSPK_SAMPLE_RATE >> 1) 41 #define PCSPK_MIN_COUNT DIV_ROUND_UP(PIT_FREQ, PCSPK_MAX_FREQ) 42 43 typedef struct PCSpkState PCSpkState; 44 #define PC_SPEAKER(obj) OBJECT_CHECK(PCSpkState, (obj), TYPE_PC_SPEAKER) 45 46 struct PCSpkState { 47 ISADevice parent_obj; 48 49 MemoryRegion ioport; 50 uint32_t iobase; 51 uint8_t sample_buf[PCSPK_BUF_LEN]; 52 QEMUSoundCard card; 53 SWVoiceOut *voice; 54 void *pit; 55 unsigned int pit_count; 56 unsigned int samples; 57 unsigned int play_pos; 58 uint8_t data_on; 59 uint8_t dummy_refresh_clock; 60 bool migrate; 61 }; 62 63 static const char *s_spk = "pcspk"; 64 static PCSpkState *pcspk_state; 65 66 static inline void generate_samples(PCSpkState *s) 67 { 68 unsigned int i; 69 70 if (s->pit_count) { 71 const uint32_t m = PCSPK_SAMPLE_RATE * s->pit_count; 72 const uint32_t n = ((uint64_t)PIT_FREQ << 32) / m; 73 74 /* multiple of wavelength for gapless looping */ 75 s->samples = (QEMU_ALIGN_DOWN(PCSPK_BUF_LEN * PIT_FREQ, m) / (PIT_FREQ >> 1) + 1) >> 1; 76 for (i = 0; i < s->samples; ++i) 77 s->sample_buf[i] = (64 & (n * i >> 25)) - 32; 78 } else { 79 s->samples = PCSPK_BUF_LEN; 80 for (i = 0; i < PCSPK_BUF_LEN; ++i) 81 s->sample_buf[i] = 128; /* silence */ 82 } 83 } 84 85 static void pcspk_callback(void *opaque, int free) 86 { 87 PCSpkState *s = opaque; 88 PITChannelInfo ch; 89 unsigned int n; 90 91 pit_get_channel_info(s->pit, 2, &ch); 92 93 if (ch.mode != 3) { 94 return; 95 } 96 97 n = ch.initial_count; 98 /* avoid frequencies that are not reproducible with sample rate */ 99 if (n < PCSPK_MIN_COUNT) 100 n = 0; 101 102 if (s->pit_count != n) { 103 s->pit_count = n; 104 s->play_pos = 0; 105 generate_samples(s); 106 } 107 108 while (free > 0) { 109 n = MIN(s->samples - s->play_pos, (unsigned int)free); 110 n = AUD_write(s->voice, &s->sample_buf[s->play_pos], n); 111 if (!n) 112 break; 113 s->play_pos = (s->play_pos + n) % s->samples; 114 free -= n; 115 } 116 } 117 118 static int pcspk_audio_init(PCSpkState *s) 119 { 120 struct audsettings as = {PCSPK_SAMPLE_RATE, 1, AUDIO_FORMAT_U8, 0}; 121 122 if (s->voice) { 123 /* already initialized */ 124 return 0; 125 } 126 127 AUD_register_card(s_spk, &s->card); 128 129 s->voice = AUD_open_out(&s->card, s->voice, s_spk, s, pcspk_callback, &as); 130 if (!s->voice) { 131 AUD_log(s_spk, "Could not open voice\n"); 132 return -1; 133 } 134 135 return 0; 136 } 137 138 static uint64_t pcspk_io_read(void *opaque, hwaddr addr, 139 unsigned size) 140 { 141 PCSpkState *s = opaque; 142 PITChannelInfo ch; 143 144 pit_get_channel_info(s->pit, 2, &ch); 145 146 s->dummy_refresh_clock ^= (1 << 4); 147 148 return ch.gate | (s->data_on << 1) | s->dummy_refresh_clock | 149 (ch.out << 5); 150 } 151 152 static void pcspk_io_write(void *opaque, hwaddr addr, uint64_t val, 153 unsigned size) 154 { 155 PCSpkState *s = opaque; 156 const int gate = val & 1; 157 158 s->data_on = (val >> 1) & 1; 159 pit_set_gate(s->pit, 2, gate); 160 if (s->voice) { 161 if (gate) /* restart */ 162 s->play_pos = 0; 163 AUD_set_active_out(s->voice, gate & s->data_on); 164 } 165 } 166 167 static const MemoryRegionOps pcspk_io_ops = { 168 .read = pcspk_io_read, 169 .write = pcspk_io_write, 170 .impl = { 171 .min_access_size = 1, 172 .max_access_size = 1, 173 }, 174 }; 175 176 static void pcspk_initfn(Object *obj) 177 { 178 PCSpkState *s = PC_SPEAKER(obj); 179 180 memory_region_init_io(&s->ioport, OBJECT(s), &pcspk_io_ops, s, "pcspk", 1); 181 182 object_property_add_link(obj, "pit", TYPE_PIT_COMMON, 183 (Object **)&s->pit, 184 qdev_prop_allow_set_link_before_realize, 185 0); 186 } 187 188 static void pcspk_realizefn(DeviceState *dev, Error **errp) 189 { 190 ISADevice *isadev = ISA_DEVICE(dev); 191 PCSpkState *s = PC_SPEAKER(dev); 192 193 isa_register_ioport(isadev, &s->ioport, s->iobase); 194 195 if (s->card.state) { 196 pcspk_audio_init(s); 197 } 198 199 pcspk_state = s; 200 } 201 202 static bool migrate_needed(void *opaque) 203 { 204 PCSpkState *s = opaque; 205 206 return s->migrate; 207 } 208 209 static const VMStateDescription vmstate_spk = { 210 .name = "pcspk", 211 .version_id = 1, 212 .minimum_version_id = 1, 213 .minimum_version_id_old = 1, 214 .needed = migrate_needed, 215 .fields = (VMStateField[]) { 216 VMSTATE_UINT8(data_on, PCSpkState), 217 VMSTATE_UINT8(dummy_refresh_clock, PCSpkState), 218 VMSTATE_END_OF_LIST() 219 } 220 }; 221 222 static Property pcspk_properties[] = { 223 DEFINE_AUDIO_PROPERTIES(PCSpkState, card), 224 DEFINE_PROP_UINT32("iobase", PCSpkState, iobase, 0x61), 225 DEFINE_PROP_BOOL("migrate", PCSpkState, migrate, true), 226 DEFINE_PROP_END_OF_LIST(), 227 }; 228 229 static void pcspk_class_initfn(ObjectClass *klass, void *data) 230 { 231 DeviceClass *dc = DEVICE_CLASS(klass); 232 233 dc->realize = pcspk_realizefn; 234 set_bit(DEVICE_CATEGORY_SOUND, dc->categories); 235 dc->vmsd = &vmstate_spk; 236 device_class_set_props(dc, pcspk_properties); 237 /* Reason: realize sets global pcspk_state */ 238 /* Reason: pit object link */ 239 dc->user_creatable = false; 240 } 241 242 static const TypeInfo pcspk_info = { 243 .name = TYPE_PC_SPEAKER, 244 .parent = TYPE_ISA_DEVICE, 245 .instance_size = sizeof(PCSpkState), 246 .instance_init = pcspk_initfn, 247 .class_init = pcspk_class_initfn, 248 }; 249 250 static int pcspk_audio_init_soundhw(ISABus *bus) 251 { 252 PCSpkState *s = pcspk_state; 253 254 warn_report("'-soundhw pcspk' is deprecated, " 255 "please set a backend using '-machine pcspk-audiodev=<name>' instead"); 256 return pcspk_audio_init(s); 257 } 258 259 static void pcspk_register(void) 260 { 261 type_register_static(&pcspk_info); 262 isa_register_soundhw("pcspk", "PC speaker", pcspk_audio_init_soundhw); 263 } 264 type_init(pcspk_register) 265