Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Jean THOMAS committed Feb 7, 2017
0 parents commit 7ae0992
Show file tree
Hide file tree
Showing 7 changed files with 384 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
bin/*
obj/*

!bin/.gitkeep
!obj/.gitkeep
33 changes: 33 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# project name (generate executable with this name)
TARGET = trinityenabler

# compiling flags here
CFLAGS = -std=c99 -Wall -I. -pedantic -Werror -O3

# linking flags here
LFLAGS = -Wall -I. -lm -framework IOKit -framework Foundation

# change these to proper directories where each file should be
SRCDIR = src
OBJDIR = obj
BINDIR = bin

SOURCES := $(wildcard $(SRCDIR)/*.c)
INCLUDES := $(wildcard $(SRCDIR)/*.h)
OBJECTS := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
rm = rm -f


$(BINDIR)/$(TARGET): $(OBJECTS)
$(CC) -o $@ $(LFLAGS) $(OBJECTS)

$(OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.c
$(CC) $(CFLAGS) -c $< -o $@

.PHONY: clean
clean:
$(rm) $(OBJECTS)

.PHONY: remove
remove: clean
$(rm) $(BINDIR)/$(TARGET)
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Trinity Enabler

Apple Pro Speakers (codenamed "Trinity") feature a built-in Micronas UAC3552A USB DAC chip. This chip needs some firmware and some parameters (EQ settings) from the host computer to work properly. Apple's driver took care of this. Unfortunately, Apple Pro Speakers was dropped from Mac OS X when Snow Leopard came out.

This utility provides Apple Pro Speakers support in recent macOS versions.

## Howto ##

### Using release builds ###

Download the utility from the [release page](https://github.com/jeanthom/trinityenabler/releases). Drag'n'drop it into a terminal, and type in the matching power rating for your USB port (in most cases it will be `--power-500` or `--power-1500`). Hit the enter key, and your Apple Pro Speakers should be working :)

### Compile it yourself ###

```
make
bin/trinityenabler --power-500
```
Empty file added bin/.gitkeep
Empty file.
Empty file added obj/.gitkeep
Empty file.
227 changes: 227 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/*
Copyright (c) 2017 Jean THOMAS.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include <stdio.h>
#include <string.h>
#include <CoreFoundation/CoreFoundation.h>
#include <libkern/OSByteOrder.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/usb/USBSpec.h>
#include "main.h"

UInt8 disableplugin_value= 0xba;

int main(int argc, char *argv[]) {
int i, ret;
enum TrinityAvailablePower availablePower;
IOUSBDeviceInterface300** deviceInterface;

/* Reading power delivery capacity */
availablePower = POWER_NULL;
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "--power-500") == 0) {
printf("Audio device set to 500mA\n");
availablePower = POWER_500MA;
} else if (strcmp(argv[i], "--power-1500") == 0) {
printf("Audio device set to 1500mA\n");
availablePower = POWER_1500MA;
} else if (strcmp(argv[i], "--power-3000") == 0) {
printf("Audio device set to 3000mA\n");
availablePower = POWER_3000MA;
} else if (strcmp(argv[i], "--power-4000") == 0) {
printf("Audio device set to 4000mA\n");
availablePower = POWER_4000MA;
}
}

if (availablePower == POWER_NULL) {
printf("Available power settings :\n");
printf("\t--power-500\t500mA\n");
printf("\t--power-1500\t1500mA\n");
printf("\t--power-3000\t3000mA\n");
printf("\t--power-4000\t4000mA\n");

return EXIT_SUCCESS;
}

/* Getting USB device interface */
deviceInterface = usbDeviceInterfaceFromVIDPID(0x05AC,0x1101);
if (deviceInterface == NULL) {
return EXIT_FAILURE;
}

/* Opening USB device interface */
ret = (*deviceInterface)->USBDeviceOpen(deviceInterface);
if (ret == kIOReturnSuccess) {

} else if (ret == kIOReturnExclusiveAccess) {
printf("HID manager has already taken care of this device. Let's try anyway.\n");
} else {
printf("Could not open device. Quitting…\n");
return EXIT_FAILURE;
}

if (disablePlugin(deviceInterface) != kIOReturnSuccess) {
printf("Error while disabling plugin.\n");
return EXIT_FAILURE;
}
if (downloadEQ(deviceInterface, availablePower) != kIOReturnSuccess) {
printf("Error while downloading EQ to Trinity audio device.\n");
return EXIT_FAILURE;
}
if (downloadPlugin(deviceInterface) != kIOReturnSuccess) {
printf("Error while downloading plugin to Trinity audio device.\n");
return EXIT_FAILURE;
}
if (enablePlugin(deviceInterface) != kIOReturnSuccess) {
printf("Error while enabling plugin.\n");
return EXIT_FAILURE;
}

/* Closing the USB device */
(*deviceInterface)->USBDeviceClose(deviceInterface);

return EXIT_SUCCESS;
}

IOUSBDeviceInterface300** usbDeviceInterfaceFromVIDPID(SInt32 vid, SInt32 pid) {
CFMutableDictionaryRef matchingDict;
io_iterator_t usbRefIterator;
io_service_t usbRef;
IOCFPlugInInterface** plugin;
IOUSBDeviceInterface300** deviceInterface;
SInt32 score;

/* Creating a matching dictionary to match the device's PID and VID */
matchingDict = IOServiceMatching(kIOUSBDeviceClassName);
CFDictionaryAddValue(matchingDict,
CFSTR(kUSBVendorID),
CFNumberCreate(kCFAllocatorDefault,
kCFNumberSInt32Type,
&vid));
CFDictionaryAddValue(matchingDict,
CFSTR(kUSBProductID),
CFNumberCreate(kCFAllocatorDefault,
kCFNumberSInt32Type,
&pid));

/* Getting all the devices that are matched by the matching dictionary */
IOServiceGetMatchingServices(kIOMasterPortDefault,
matchingDict,
&usbRefIterator);

/* We only use the first USB device */
usbRef = IOIteratorNext(usbRefIterator);
IOObjectRelease(usbRefIterator);

if (usbRef == 0) {
printf("%s : Can't find suitable USB audio device\n", __PRETTY_FUNCTION__);
return NULL;
} else {
IOCreatePlugInInterfaceForService(usbRef,
kIOUSBDeviceUserClientTypeID,
kIOCFPlugInInterfaceID,
&plugin,
&score);
IOObjectRelease(usbRef);
(*plugin)->QueryInterface(plugin,
CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID300),
(LPVOID)&deviceInterface);
(*plugin)->Release(plugin);

return deviceInterface;
}
}

IOReturn xdfpSetMem(IOUSBDeviceInterface300** deviceInterface, UInt8 *buf, UInt16 length, UInt16 xdfpAddr) {
IOUSBDevRequest devReq;

devReq.bmRequestType = USBmakebmRequestType(kUSBOut, kUSBVendor, kUSBDevice);
devReq.bRequest = kMicronasSetMemReq;
devReq.wValue = 0;
devReq.wIndex = xdfpAddr;
devReq.wLength = length;
devReq.pData = buf;

return (*deviceInterface)->DeviceRequest(deviceInterface, &devReq);
}

IOReturn xdfpWrite(IOUSBDeviceInterface300** deviceInterface, UInt16 xdfpAddr, SInt32 value) {
static UInt8 xdfpData[5];

if (value < 0) value += 0x40000;
xdfpData[0] = (value >> 10) & 0xff;
xdfpData[1] = (value >> 2) & 0xff;
xdfpData[2] = value & 0x03;
xdfpData[3] = (xdfpAddr >> 8) & 0x03;
xdfpData[4] = xdfpAddr & 0xff;

return xdfpSetMem(deviceInterface, xdfpData, 5, V8_WRITE_START_ADDR);
}

IOReturn downloadEQ(IOUSBDeviceInterface300** deviceInterface, enum TrinityAvailablePower availablePower) {
UInt16 xdfpAddr;
UInt32 eqIndex;
IOReturn ret;
static SInt32 *eqSettings;

switch (availablePower) {
case POWER_4000MA:
eqSettings = power4AEQSettings;
break;
case POWER_3000MA:
eqSettings = power3AEQSettings;
break;
case POWER_1500MA:
eqSettings = power1500mAEQSettings;
break;
default:
case POWER_500MA:
eqSettings = power500mAEQSettings;
break;
}

for (eqIndex = 0, xdfpAddr = XDFP_STARTING_EQ_ADDR; eqIndex < EQ_TABLE_SIZE; eqIndex++, xdfpAddr++) {
ret = xdfpWrite(deviceInterface, xdfpAddr, eqSettings[eqIndex]);
if (ret != kIOReturnSuccess) {
return ret;
}

nanosleep((const struct timespec[]){{0, 3000000L}}, NULL);
}

return ret;
}

IOReturn disablePlugin(IOUSBDeviceInterface300** deviceInterface) {
return xdfpSetMem(deviceInterface, &disableplugin_value, 1, V8_PLUGIN_START_ADDR);
}

IOReturn enablePlugin(IOUSBDeviceInterface300** deviceInterface) {
return xdfpSetMem(deviceInterface, pluginBinary, 1, V8_PLUGIN_START_ADDR);
}

IOReturn downloadPlugin(IOUSBDeviceInterface300** deviceInterface) {
return xdfpSetMem(deviceInterface, &pluginBinary[1], sizeof(pluginBinary), V8_PLUGIN_START_ADDR+1);
}
101 changes: 101 additions & 0 deletions src/main.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
Copyright (c) 2017 Jean THOMAS.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

/* 0xB042-0xB044 is XDFP (DSP interface) data in */
/* 0xB045-0xB046 is XDFP address/command interface */
/* 0xB05C-0xB05E is XDFP data out (unused here) */
#define V8_WRITE_START_ADDR 0xb042

/* This memory address is mapped to the RAM */
#define V8_PLUGIN_START_ADDR 0x8120
#define XDFP_STARTING_EQ_ADDR 0x50

#define EQ_TABLE_SIZE 16

#define kMicronasSetMemReq 4
#define kMicronasGetMemReq 5136

enum TrinityAvailablePower {
POWER_NULL, POWER_500MA, POWER_1500MA, POWER_3000MA, POWER_4000MA
};

/* These EQ come from AppleUSBTrinityAudioDevice.cpp */
static SInt32 power4AEQSettings[] = {
228, -129968, 130513,
-279, -125942, 128415,
-1689, -123355, 126686,
-5136, -95891, 109553,
-18995, -993, 6924,
-45000
};

static SInt32 power3AEQSettings[] = {
228, -129968, 130513,
-279, -125942, 128415,
-1689, -123355, 126686,
-5137, -95891, 109553,
-18995, -993, 6924,
-42000};

static SInt32 power1500mAEQSettings[] = {
228, -129968, 130513,
-279, -125942, 128415,
-1689, -123355, 126686,
-5137, -95891, 109553,
-18995, -993, 6924,
-20000};

static SInt32 power500mAEQSettings[] = {
228, -129968, 130513,
-279, -125942, 128415,
-1689, -123355, 126686,
-5137, -95891, 109553,
-18995, -993, 6924,
-8000};

/* Plugins are firmware extensions. The plugin is necessary to enable the amplifier chip. */
static UInt8 pluginBinary[] = {
0xBF, 0x35, 0x81, 0xBA, 0x85, 0xEA, 0x7B, 0x80, 0xE1, 0x13, 0xBF, 0xDE, 0x0B, 0x8D, 0xB9, 0x85,
0xBF, 0x1A, 0x0C, 0x8D, 0xB9, 0xE9, 0xF3, 0x81, 0xE8, 0x80, 0x80, 0x79, 0x90, 0x03, 0xBC, 0xEC,
0x81, 0xEA, 0xA2, 0xB0, 0xE4, 0x00, 0xE5, 0x02, 0x44, 0x99, 0x01, 0x45, 0x15, 0x71, 0x14, 0x19,
0x90, 0xF6, 0xE0, 0xF0, 0xC8, 0x87, 0x80, 0xC8, 0x51, 0xB0, 0x12, 0x63, 0x90, 0x03, 0x28, 0x98,
0x02, 0xE0, 0x40, 0xC8, 0x89, 0x80, 0xC8, 0xA0, 0xB0, 0xE1, 0xFB, 0x12, 0x21, 0xC8, 0x88, 0x80,
0xE8, 0x80, 0x80, 0x90, 0x09, 0xE8, 0x01, 0xA0, 0xC8, 0x80, 0x80, 0xBF, 0x2F, 0x81, 0xE8, 0x80,
0x80, 0xC8, 0xF3, 0x81, 0xE1, 0x0C, 0x12, 0x21, 0x90, 0x25, 0xE9, 0xED, 0x81, 0xE8, 0x7B, 0x80,
0x59, 0x49, 0x74, 0xE9, 0xF0, 0x81, 0xE2, 0x80, 0x2A, 0x73, 0x11, 0x2A, 0x7B, 0x99, 0x0A, 0xE8,
0xF1, 0x81, 0x00, 0xC8, 0xF1, 0x81, 0xCC, 0x7B, 0x80, 0xE8, 0xEE, 0x81, 0xBC, 0xDA, 0x81, 0xE8,
0xF2, 0x81, 0x90, 0x29, 0xE9, 0x7B, 0x80, 0xE8, 0xED, 0x81, 0x51, 0x72, 0xE8, 0xF1, 0x81, 0x98,
0x16, 0x40, 0xC8, 0xF1, 0x81, 0x12, 0xE1, 0x80, 0x29, 0xE1, 0xB0, 0x79, 0x99, 0x04, 0x12, 0xBC,
0xD4, 0x81, 0xE0, 0x30, 0xC8, 0x7B, 0x80, 0xE8, 0xEF, 0x81, 0xC8, 0xF2, 0x81, 0xE8, 0xF2, 0x81,
0x90, 0x03, 0xBC, 0x24, 0x81, 0x40, 0xC8, 0xF2, 0x81, 0xBC, 0x24, 0x81, 0xB9, 0x01, 0x08, 0x0F,
0xD0, 0x01, 0x01, 0x01
};


int main(int argc, char *argv[]);
IOReturn xdfpSetMem(IOUSBDeviceInterface300** deviceInterface, UInt8 *buf, UInt16 length, UInt16 xdfpAddr);
IOReturn xdfpWrite(IOUSBDeviceInterface300** deviceInterface, UInt16 xdfpAddr, SInt32 value);
IOReturn downloadEQ(IOUSBDeviceInterface300** deviceInterface, enum TrinityAvailablePower availablePower);
IOReturn downloadPlugin(IOUSBDeviceInterface300** deviceInterface);
IOReturn disablePlugin(IOUSBDeviceInterface300** deviceInterface);
IOReturn enablePlugin(IOUSBDeviceInterface300** deviceInterface);
IOUSBDeviceInterface300** usbDeviceInterfaceFromVIDPID(SInt32 vid, SInt32 pid);

0 comments on commit 7ae0992

Please sign in to comment.