From f9e97036cf0680025f9e3d8579f6d00d2b8b1acf Mon Sep 17 00:00:00 2001 From: Michael Herger Date: Sun, 4 Apr 2021 19:02:12 +0200 Subject: [PATCH 1/5] Firmware proxy (#88) * Add support for a firmware download proxy. This should help in situations where the player's firmware can't handle https correctly. Two possibilities: * full path to image: http://yourlms:9000/plugins/SqueezeESP32/firmware/ESP32-A1S.32.634.master-cmake/squeezelite-esp32-master-cmake-ESP32-A1S-32-V0.634.bin * use Github's asset ID: http://yourlms:9000/plugins/SqueezeESP32/firmware/34298863 The former is more prone to issues related to the path. A change in the schema could break the matching regex. The latter is simpler to use if you know the ID. But the ID is not easily available to the user. And it requires one more lookup in the plugin to get from the ID to the download path. * Add support for proxying firmware downloads through LMS * add magic asset ID -99 to allow the front-end to check whether the plugin does support download proxying * web manager is expecting `lms_port` and `lms_ip` in `status.json`. If that's available, check whether plugin does support firmware downloading. If that's the case, download firmwares through LMS * plugin would cache firmware images. In case of multiple images the file would be served directly from LMS. * Add firmware pre-caching * keep track of the most recently requested firmware build type * poll Github for releases every ~6h * download new firmware file for the same player model used before Factor out firmware handling code to its own module. Co-authored-by: Michael Herger --- plugin/SqueezeESP32/FirmwareHelper.pm | 237 ++++++++++++++++++++++++++ plugin/SqueezeESP32/Plugin.pm | 147 +--------------- 2 files changed, 240 insertions(+), 144 deletions(-) create mode 100644 plugin/SqueezeESP32/FirmwareHelper.pm diff --git a/plugin/SqueezeESP32/FirmwareHelper.pm b/plugin/SqueezeESP32/FirmwareHelper.pm new file mode 100644 index 00000000..3f0c192e --- /dev/null +++ b/plugin/SqueezeESP32/FirmwareHelper.pm @@ -0,0 +1,237 @@ +package Plugins::SqueezeESP32::FirmwareHelper; + +use strict; + +use File::Basename qw(basename); +use File::Spec::Functions qw(catfile); +use JSON::XS::VersionOneAndTwo; + +use Slim::Utils::Log; +use Slim::Utils::Prefs; + +use constant FIRMWARE_POLL_INTERVAL => 3600 * (5 + rand()); +use constant GITHUB_RELEASES_URI => "https://api.github.com/repos/sle118/squeezelite-esp32/releases"; +use constant GITHUB_ASSET_URI => GITHUB_RELEASES_URI . "/assets/"; +use constant GITHUB_DOWNLOAD_URI => "https://github.com/sle118/squeezelite-esp32/releases/download/"; +my $FW_DOWNLOAD_ID_REGEX = qr|plugins/SqueezeESP32/firmware/(-?\d+)|; +my $FW_DOWNLOAD_REGEX = qr|plugins/SqueezeESP32/firmware/([-a-z0-9-/.]+\.bin)$|i; +my $FW_FILENAME_REGEX = qr/^squeezelite-esp32-.*\.bin(\.tmp)?$/; +my $FW_TAG_REGEX = qr/\/(ESP32-A1S|SqueezeAmp|I2S-4MFlash)\.(16|32)\.(\d+)\.(.*)\//; + +my $prefs = preferences('plugin.squeezeesp32'); +my $log = logger('plugin.squeezeesp32'); + +sub init { + Slim::Web::Pages->addRawFunction($FW_DOWNLOAD_ID_REGEX, \&handleFirmwareDownload); + Slim::Web::Pages->addRawFunction($FW_DOWNLOAD_REGEX, \&handleFirmwareDownloadDirect); + + # start checking for firmware updates + Slim::Utils::Timers::setTimer(undef, Time::HiRes::time() + 30 + rand(30), \&prefetchFirmware); +} + +sub prefetchFirmware { + Slim::Utils::Timers::killTimers(undef, \&prefetchFirmware); + my $releaseInfo = $prefs->get('lastReleaseTagUsed'); + + Slim::Networking::SimpleAsyncHTTP->new( + sub { + my $http = shift; + my $content = eval { from_json( $http->content ) }; + + if (!$content || !ref $content) { + $@ && $log->error("Failed to parse response: $@"); + } + + my $regex = $releaseInfo->{model} . '\.' . $releaseInfo->{res} . '\.\d+\.' . $releaseInfo->{branch}; + my $url; + foreach (@$content) { + if ($_->{tag_name} =~ /$regex/ && $_->{assets} && ref $_->{assets}) { + ($url) = grep /\.bin$/, map { + $_->{browser_download_url} + } @{$_->{assets}}; + + last if $url; + } + } + + downloadFirmwareFile(sub { + main::INFOLOG && $log->is_info && $log->info("Pre-cached firmware file: " . $_[0]); + }, sub { + my ($http, $error, $url, $code) = @_; + $error ||= ($http && $http->error) || 'unknown error'; + $url ||= ($http && $http->url) || 'no URL'; + + $log->error(sprintf("Failed to get firmware image from Github: %s (%s)", $error || $http->error, $url)); + }, $url) if $url && $url =~ /^https?/; + + }, + sub { + my ($http, $error) = @_; + $log->error("Failed to get releases from Github: $error"); + }, + { + timeout => 10, + cache => 1, + expires => 3600 + } + )->get(GITHUB_RELEASES_URI) if $releaseInfo; + + Slim::Utils::Timers::setTimer(undef, Time::HiRes::time() + FIRMWARE_POLL_INTERVAL, \&prefetchFirmware); +} + +sub handleFirmwareDownload { + my ($httpClient, $response) = @_; + + my $request = $response->request; + + my $_errorDownloading = sub { + _errorDownloading($httpClient, $response, @_); + }; + + my $id; + if (!defined $request || !(($id) = $request->uri =~ $FW_DOWNLOAD_ID_REGEX)) { + return $_errorDownloading->(undef, 'Invalid request', $request->uri, 400); + } + + # this is the magic number used on the client to figure out whether the plugin does support download proxying + if ($id == -99) { + $response->code(204); + $response->header('Access-Control-Allow-Origin' => '*'); + + $httpClient->send_response($response); + return Slim::Web::HTTP::closeHTTPSocket($httpClient); + } + + Slim::Networking::SimpleAsyncHTTP->new( + sub { + my $http = shift; + my $content = eval { from_json( $http->content ) }; + + if (!$content || !ref $content) { + $@ && $log->error("Failed to parse response: $@"); + return $_errorDownloading->($http); + } + elsif (!$content->{browser_download_url} || !$content->{name}) { + return $_errorDownloading->($http, 'No download URL found'); + } + + downloadFirmwareFile(sub { + my $firmwareFile = shift; + $response->code(200); + Slim::Web::HTTP::sendStreamingFile($httpClient, $response, 'application/octet-stream', $firmwareFile, undef, 1); + }, $_errorDownloading, $content->{browser_download_url}, $content->{name}); + }, + $_errorDownloading, + { + timeout => 10, + cache => 1, + expires => 86400 + } + )->get(GITHUB_ASSET_URI . $id); + + return; +} + +sub handleFirmwareDownloadDirect { + my ($httpClient, $response) = @_; + + my $request = $response->request; + + my $_errorDownloading = sub { + _errorDownloading($httpClient, $response, @_); + }; + + my $path; + if (!defined $request || !(($path) = $request->uri =~ $FW_DOWNLOAD_REGEX)) { + return $_errorDownloading->(undef, 'Invalid request', $request->uri, 400); + } + + main::INFOLOG && $log->is_info && $log->info("Requesting firmware from: $path"); + + downloadFirmwareFile(sub { + my $firmwareFile = shift; + $response->code(200); + Slim::Web::HTTP::sendStreamingFile($httpClient, $response, 'application/octet-stream', $firmwareFile, undef, 1); + }, $_errorDownloading, GITHUB_DOWNLOAD_URI . $path); +} + +sub downloadFirmwareFile { + my ($cb, $ecb, $url, $name) = @_; + + # keep track of the last firmware we requested, to prefetch it in the future + _getFirmwareTag($url); + + $name ||= basename($url); + + if ($name !~ $FW_FILENAME_REGEX) { + return $ecb->(undef, 'Unexpected firmware image name: ' . $name, $url, 400); + } + + my $updatesDir = Slim::Utils::OSDetect::dirsFor('updates'); + my $firmwareFile = catfile($updatesDir, $name); + Slim::Utils::Misc::deleteFiles($updatesDir, $FW_FILENAME_REGEX, $firmwareFile); + + if (-f $firmwareFile) { + main::INFOLOG && $log->is_info && $log->info("Found cached firmware file"); + return $cb->($firmwareFile); + } + + Slim::Networking::SimpleAsyncHTTP->new( + sub { + my $http = shift; + + if ($http->code != 200 || !-e "$firmwareFile.tmp") { + return $ecb->($http, $http->mess); + } + + rename "$firmwareFile.tmp", $firmwareFile or return $ecb->($http, "Unable to rename temporary $firmwareFile file" ); + + return $cb->($firmwareFile); + }, + $ecb, + { + saveAs => "$firmwareFile.tmp", + } + )->get($url); + + return; +} + +sub _getFirmwareTag { + my ($url) = @_; + + if (my ($model, $resolution, $version, $branch) = $url =~ $FW_TAG_REGEX) { + my $releaseInfo = { + model => $model, + res => $resolution, + version => $version, + branch => $branch + }; + + $prefs->set('lastReleaseTagUsed', $releaseInfo); + + return $releaseInfo; + } +} + +sub _errorDownloading { + my ($httpClient, $response, $http, $error, $url, $code) = @_; + + $error ||= ($http && $http->error) || 'unknown error'; + $url ||= ($http && $http->url) || 'no URL'; + $code ||= ($http && $http->code) || 500; + + $log->error(sprintf("Failed to get data from Github: %s (%s)", $error || $http->error, $url)); + + $response->headers->remove_content_headers; + $response->code($code); + $response->content_type('text/plain'); + $response->header('Connection' => 'close'); + $response->content(''); + + $httpClient->send_response($response); + Slim::Web::HTTP::closeHTTPSocket($httpClient); +}; + + +1; \ No newline at end of file diff --git a/plugin/SqueezeESP32/Plugin.pm b/plugin/SqueezeESP32/Plugin.pm index 9ba733a6..6614c179 100644 --- a/plugin/SqueezeESP32/Plugin.pm +++ b/plugin/SqueezeESP32/Plugin.pm @@ -3,14 +3,13 @@ package Plugins::SqueezeESP32::Plugin; use strict; use base qw(Slim::Plugin::Base); -use File::Basename qw(basename); -use File::Spec::Functions qw(catfile); -use JSON::XS::VersionOneAndTwo; use Slim::Utils::Prefs; use Slim::Utils::Log; use Slim::Web::ImageProxy; +use Plugins::SqueezeESP32::FirmwareHelper; + my $prefs = preferences('plugin.squeezeesp32'); my $log = Slim::Utils::Log->addLogCategory({ @@ -19,12 +18,6 @@ my $log = Slim::Utils::Log->addLogCategory({ 'description' => 'PLUGIN_SQUEEZEESP32', }); -use constant GITHUB_ASSET_URI => "https://api.github.com/repos/sle118/squeezelite-esp32/releases/assets/"; -use constant GITHUB_DOWNLOAD_URI => "https://github.com/sle118/squeezelite-esp32/releases/download/"; -my $FW_DOWNLOAD_ID_REGEX = qr|plugins/SqueezeESP32/firmware/(-?\d+)|; -my $FW_DOWNLOAD_REGEX = qr|plugins/SqueezeESP32/firmware/([-a-z0-9-/.]+\.bin)$|i; -my $FW_FILENAME_REGEX = qr/^squeezelite-esp32-.*\.bin(\.tmp)?$/; - # migrate 'eq' pref, as that's a reserved word and could cause problems in the future $prefs->migrateClient(1, sub { my ($cprefs, $client) = @_; @@ -68,8 +61,7 @@ sub initPlugin { Slim::Control::Request::subscribe( sub { onNotification(@_) }, [ ['playlist'], ['open', 'newsong'] ]); Slim::Control::Request::subscribe( \&onStopClear, [ ['playlist'], ['stop', 'clear'] ]); - Slim::Web::Pages->addRawFunction($FW_DOWNLOAD_ID_REGEX, \&handleFirmwareDownload); - Slim::Web::Pages->addRawFunction($FW_DOWNLOAD_REGEX, \&handleFirmwareDownloadDirect); + Plugins::SqueezeESP32::FirmwareHelper->init(); } sub onStopClear { @@ -111,137 +103,4 @@ sub setEQ { $client->send_equalizer(\@eqParams); } -sub handleFirmwareDownload { - my ($httpClient, $response) = @_; - - my $request = $response->request; - - my $_errorDownloading = sub { - _errorDownloading($httpClient, $response, @_); - }; - - my $id; - if (!defined $request || !(($id) = $request->uri =~ $FW_DOWNLOAD_ID_REGEX)) { - return $_errorDownloading->(undef, 'Invalid request', $request->uri, 400); - } - - # this is the magic number used on the client to figure out whether the plugin does support download proxying - if ($id == -99) { - $response->code(204); - $response->header('Access-Control-Allow-Origin' => '*'); - - $httpClient->send_response($response); - return Slim::Web::HTTP::closeHTTPSocket($httpClient); - } - - Slim::Networking::SimpleAsyncHTTP->new( - sub { - my $http = shift; - my $content = eval { from_json( $http->content ) }; - - if (!$content || !ref $content) { - $@ && $log->error("Failed to parse response: $@"); - return $_errorDownloading->($http); - } - elsif (!$content->{browser_download_url} || !$content->{name}) { - return $_errorDownloading->($http, 'No download URL found'); - } - - downloadAndStreamFirmware($httpClient, $response, $content->{browser_download_url}, $content->{name}); - }, - $_errorDownloading, - { - timeout => 10, - cache => 1, - expires => 86400 - } - )->get(GITHUB_ASSET_URI . $id); - - return; -} - -sub handleFirmwareDownloadDirect { - my ($httpClient, $response) = @_; - - my $request = $response->request; - - my $_errorDownloading = sub { - _errorDownloading($httpClient, $response, @_); - }; - - my $path; - if (!defined $request || !(($path) = $request->uri =~ $FW_DOWNLOAD_REGEX)) { - return $_errorDownloading->(undef, 'Invalid request', $request->uri, 400); - } - - main::INFOLOG && $log->is_info && $log->info("Requesting firmware from: $path"); - - downloadAndStreamFirmware($httpClient, $response, GITHUB_DOWNLOAD_URI . $path); -} - -sub downloadAndStreamFirmware { - my ($httpClient, $response, $url, $name) = @_; - - my $_errorDownloading = sub { - _errorDownloading($httpClient, $response, @_); - }; - - $name ||= basename($url); - - if ($name !~ $FW_FILENAME_REGEX) { - return $_errorDownloading->(undef, 'Unexpected firmware image name: ' . $name, $url, 400); - } - - my $updatesDir = Slim::Utils::OSDetect::dirsFor('updates'); - my $firmwareFile = catfile($updatesDir, $name); - Slim::Utils::Misc::deleteFiles($updatesDir, $FW_FILENAME_REGEX, $firmwareFile); - - if (-f $firmwareFile) { - main::INFOLOG && $log->is_info && $log->info("Found cached firmware version"); - $response->code(200); - return Slim::Web::HTTP::sendStreamingFile($httpClient, $response, 'application/octet-stream', $firmwareFile, undef, 1); - } - - Slim::Networking::SimpleAsyncHTTP->new( - sub { - my $http = shift; - - if ($http->code != 200 || !-e "$firmwareFile.tmp") { - return $_errorDownloading->($http, $http->mess); - } - - rename "$firmwareFile.tmp", $firmwareFile or return $_errorDownloading->($http, "Unable to rename temporary $firmwareFile file" ); - - $response->code(200); - Slim::Web::HTTP::sendStreamingFile($httpClient, $response, 'application/octet-stream', $firmwareFile, undef, 1); - }, - $_errorDownloading, - { - saveAs => "$firmwareFile.tmp", - } - )->get($url); - - return; -} - -sub _errorDownloading { - my ($httpClient, $response, $http, $error, $url, $code) = @_; - - $error ||= ($http && $http->error) || 'unknown error'; - $url ||= ($http && $http->url) || 'no URL'; - $code ||= ($http && $http->code) || 500; - - $log->error(sprintf("Failed to get data from Github: %s (%s)", $error || $http->error, $url)); - - $response->headers->remove_content_headers; - $response->code($code); - $response->content_type('text/plain'); - $response->header('Connection' => 'close'); - $response->content(''); - - $httpClient->send_response($response); - Slim::Web::HTTP::closeHTTPSocket($httpClient); -}; - - 1; From f6b55c5ac90a4425a64aae863ac6ff83f89513f2 Mon Sep 17 00:00:00 2001 From: Philippe G Date: Sun, 4 Apr 2021 15:45:22 -0700 Subject: [PATCH 2/5] voltage --- plugin/SqueezeESP32/Player.pm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugin/SqueezeESP32/Player.pm b/plugin/SqueezeESP32/Player.pm index 158d1395..d82b3b5c 100644 --- a/plugin/SqueezeESP32/Player.pm +++ b/plugin/SqueezeESP32/Player.pm @@ -343,4 +343,9 @@ sub lineInOutStatus { } } +sub voltage { + my $voltage = Slim::Networking::Slimproto::voltage(shift) || return 0; + return sprintf("%.2f", ($voltage >> 4) / 128); +} + 1; From 3a2bfe470ff5df02962888b9647764703e636add Mon Sep 17 00:00:00 2001 From: Philippe G Date: Sun, 4 Apr 2021 16:06:31 -0700 Subject: [PATCH 3/5] show absolute battery level --- components/services/battery.c | 5 +++-- components/services/monitor.h | 2 +- components/squeezelite/embedded.c | 4 ++-- components/squeezelite/embedded.h | 2 +- plugin/SqueezeESP32/install.xml | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/components/services/battery.c b/components/services/battery.c index e1882b72..30e69b82 100644 --- a/components/services/battery.c +++ b/components/services/battery.c @@ -51,8 +51,9 @@ float battery_value_svc(void) { * */ uint8_t battery_level_svc(void) { - // TODO: this is totally incorrect - return battery.avg ? (battery.avg - (3.0 * battery.cells)) / ((4.2 - 3.0) * battery.cells) * 100 : 0; + // TODO: this is vastly incorrect + int level = battery.avg ? (battery.avg - (3.0 * battery.cells)) / ((4.2 - 3.0) * battery.cells) * 100 : 0; + return level < 100 ? level : 100; } /**************************************************************************************** diff --git a/components/services/monitor.h b/components/services/monitor.h index 8e255e87..3f441ff9 100644 --- a/components/services/monitor.h +++ b/components/services/monitor.h @@ -21,7 +21,7 @@ extern void (*spkfault_handler_svc)(bool inserted); extern bool spkfault_svc(void); extern float battery_value_svc(void); -extern uint8_t battery_level_svc(void); +extern uint16_t battery_level_svc(void); extern monitor_gpio_t * get_spkfault_gpio(); extern monitor_gpio_t * get_jack_insertion_gpio(); diff --git a/components/squeezelite/embedded.c b/components/squeezelite/embedded.c index dfa43771..fb0b3d5d 100644 --- a/components/squeezelite/embedded.c +++ b/components/squeezelite/embedded.c @@ -67,8 +67,8 @@ u16_t get_plugged(void) { return jack_inserted_svc() ? PLUG_HEADPHONE : 0; } -u8_t get_battery(void) { - return (battery_level_svc() * 16) / 100; +u16_t get_battery(void) { + return (u16_t) (battery_value_svc() * 128) & 0x0fff; } void set_name(char *name) { diff --git a/components/squeezelite/embedded.h b/components/squeezelite/embedded.h index 29a7563a..984cb6bc 100644 --- a/components/squeezelite/embedded.h +++ b/components/squeezelite/embedded.h @@ -77,7 +77,7 @@ extern mutex_type slimp_mutex; #define PLUG_HEADPHONE 0x04 u16_t get_RSSI(void); // must provide or define as 0xffff u16_t get_plugged(void); // must provide or define as 0x0 -u8_t get_battery(void); // must provide 0..15 or define as 0x0 +u16_t get_battery(void); // must provide 12 bits data or define as 0x0 (exact meaning is device-dependant) // set name void set_name(char *name); // can be defined as an empty macro diff --git a/plugin/SqueezeESP32/install.xml b/plugin/SqueezeESP32/install.xml index 200de627..44753dce 100644 --- a/plugin/SqueezeESP32/install.xml +++ b/plugin/SqueezeESP32/install.xml @@ -10,6 +10,6 @@ PLUGIN_SQUEEZEESP32 PLUGIN_SQUEEZEESP32_DESC Plugins::SqueezeESP32::Plugin - 0.211 + 0.310 Philippe From cac6306a04fbe12f312fae882ebb4e7c054a55d7 Mon Sep 17 00:00:00 2001 From: Philippe G Date: Wed, 7 Apr 2021 00:45:21 -0700 Subject: [PATCH 4/5] activate SBR mode in AAC --- components/squeezelite/helix-aac.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/components/squeezelite/helix-aac.c b/components/squeezelite/helix-aac.c index 2c9957c8..723db9ff 100644 --- a/components/squeezelite/helix-aac.c +++ b/components/squeezelite/helix-aac.c @@ -151,14 +151,18 @@ static int read_mp4_header(unsigned long *samplerate_p, unsigned char *channels_ LOG_WARN("error parsing esds"); return -1; } - mp4_desc_length(&ptr); + int desc_len = mp4_desc_length(&ptr); info.profile = *ptr >> 3; info.sampRateCore = (*ptr++ & 0x07) << 1; info.sampRateCore |= (*ptr >> 7) & 0x01; - info.sampRateCore = rates[info.sampRateCore]; - info.nChans = (*ptr & 0x7f) >> 3; - *channels_p = info.nChans; + info.sampRateCore = rates[info.sampRateCore]; + info.nChans = (*ptr++ & 0x7f) >> 3; + *channels_p = info.nChans; *samplerate_p = info.sampRateCore; + if (desc_len > 2 && ((ptr[0] << 3) | (ptr[1] >> 5)) == 0x2b7 && (ptr[1] & 0x1f) == 0x05 && (ptr[2] & 0x80)) { + LOG_WARN("AAC SBR mode activated => high CPU consumption (please proxy)"); + *samplerate_p = rates[(ptr[2] & 0x78) >> 3]; + } HAAC(a, SetRawBlockParams, a->hAac, 0, &info); LOG_DEBUG("playable aac track: %u (p:%x, r:%d, c:%d)", trak, info.profile, info.sampRateCore, info.nChans); play = trak; From 1b39a4f7c91954f3c0367c938804d3a2d022062c Mon Sep 17 00:00:00 2001 From: Philippe G Date: Thu, 8 Apr 2021 21:37:24 -0700 Subject: [PATCH 5/5] DAC refactoring --- .../squeezelite/.sc3357753833280144641.c | 0 components/squeezelite/ac101/ac101.c | 207 ++++++------------ components/squeezelite/adac.h | 8 + components/squeezelite/adac_core.c | 164 ++++++++++++++ .../squeezelite/external/dac_external.c | 100 ++------- components/squeezelite/helix-aac.c | 5 +- components/squeezelite/tas57xx/dac_5713.c | 117 ++-------- components/squeezelite/tas57xx/dac_57xx.c | 80 ++----- 8 files changed, 287 insertions(+), 394 deletions(-) delete mode 100644 components/squeezelite/.sc3357753833280144641.c create mode 100644 components/squeezelite/adac_core.c diff --git a/components/squeezelite/.sc3357753833280144641.c b/components/squeezelite/.sc3357753833280144641.c deleted file mode 100644 index e69de29b..00000000 diff --git a/components/squeezelite/ac101/ac101.c b/components/squeezelite/ac101/ac101.c index e3bac89d..f7227e1d 100644 --- a/components/squeezelite/ac101/ac101.c +++ b/components/squeezelite/ac101/ac101.c @@ -48,118 +48,84 @@ static const char TAG[] = "AC101"; return b;\ } -static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config); -static void deinit(void); +static bool init(char *config, int i2c_port, i2s_config_t *i2s_config); static void speaker(bool active); static void headset(bool active); static bool volume(unsigned left, unsigned right); static void power(adac_power_e mode); -const struct adac_s dac_ac101 = { "AC101", init, deinit, power, speaker, headset, volume }; +const struct adac_s dac_ac101 = { "AC101", init, adac_deinit, power, speaker, headset, volume }; -static esp_err_t i2c_write_reg(uint8_t reg, uint16_t val); -static uint16_t i2c_read_reg(uint8_t reg); static void ac101_start(ac_module_t mode); static void ac101_stop(void); static void ac101_set_earph_volume(uint8_t volume); static void ac101_set_spk_volume(uint8_t volume); -static int i2c_port; - /**************************************************************************************** * init */ -static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) { - esp_err_t res = ESP_OK; - char *p; - - // configure i2c - i2c_config_t i2c_config = { - .mode = I2C_MODE_MASTER, - .sda_io_num = -1, - .sda_pullup_en = GPIO_PULLUP_ENABLE, - .scl_io_num = -1, - .scl_pullup_en = GPIO_PULLUP_ENABLE, - .master.clk_speed = 250000, - }; - - if ((p = strcasestr(config, "sda")) != NULL) i2c_config.sda_io_num = atoi(strchr(p, '=') + 1); - if ((p = strcasestr(config, "scl")) != NULL) i2c_config.scl_io_num = atoi(strchr(p, '=') + 1); - - i2c_port = i2c_port_num; - i2c_param_config(i2c_port, &i2c_config); - i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false); - - res = i2c_read_reg(CHIP_AUDIO_RS); - - if (!res) { +static bool init(char *config, int i2c_port, i2s_config_t *i2s_config) { + adac_init(config, i2c_port); + if (adac_read_word(AC101_ADDR, CHIP_AUDIO_RS) == 0xffff) { ESP_LOGW(TAG, "No AC101 detected"); i2c_driver_delete(i2c_port); - return 0; + return false; } - res = i2c_write_reg(CHIP_AUDIO_RS, 0x123); - // huh? + ESP_LOGI(TAG, "AC101 detected"); + + adac_write_word(AC101_ADDR, CHIP_AUDIO_RS, 0x123); vTaskDelay(100 / portTICK_PERIOD_MS); // enable the PLL from BCLK source - i2c_write_reg(PLL_CTRL1, BIN(0000,0001,0100,1111)); // F=1,M=1,PLL,INT=31 (medium) - i2c_write_reg(PLL_CTRL2, BIN(1000,0110,0000,0000)); // PLL, F=96,N_i=1024-96,F=0,N_f=0*0.2; - // i2c_write_reg(PLL_CTRL2, BIN(1000,0011,1100,0000)); + adac_write_word(AC101_ADDR, PLL_CTRL1, BIN(0000,0001,0100,1111)); // F=1,M=1,PLL,INT=31 (medium) + adac_write_word(AC101_ADDR, PLL_CTRL2, BIN(1000,0110,0000,0000)); // PLL, F=96,N_i=1024-96,F=0,N_f=0*0.2; + // adac_write_word(AC101_ADDR, PLL_CTRL2, BIN(1000,0011,1100,0000)); // clocking system - i2c_write_reg(SYSCLK_CTRL, BIN(1010,1010,0000,1000)); // PLLCLK, BCLK1, IS1CLK, PLL, SYSCLK - i2c_write_reg(MOD_CLK_ENA, BIN(1000,0000,0000,1100)); // IS21, ADC, DAC - i2c_write_reg(MOD_RST_CTRL, BIN(1000,0000,0000,1100)); // IS21, ADC, DAC - i2c_write_reg(I2S_SR_CTRL, BIN(0111,0000,0000,0000)); // 44.1kHz + adac_write_word(AC101_ADDR, SYSCLK_CTRL, BIN(1010,1010,0000,1000)); // PLLCLK, BCLK1, IS1CLK, PLL, SYSCLK + adac_write_word(AC101_ADDR, MOD_CLK_ENA, BIN(1000,0000,0000,1100)); // IS21, ADC, DAC + adac_write_word(AC101_ADDR, MOD_RST_CTRL, BIN(1000,0000,0000,1100)); // IS21, ADC, DAC + adac_write_word(AC101_ADDR, I2S_SR_CTRL, BIN(0111,0000,0000,0000)); // 44.1kHz // analogue config - i2c_write_reg(I2S1LCK_CTRL, BIN(1000,1000,0101,0000)); // Slave, BCLK=I2S/8,LRCK=32,16bits,I2Smode, Stereo - i2c_write_reg(I2S1_SDOUT_CTRL, BIN(1100,0000,0000,0000)); // I2S1ADC (R&L) - i2c_write_reg(I2S1_SDIN_CTRL, BIN(1100,0000,0000,0000)); // IS21DAC (R&L) - i2c_write_reg(I2S1_MXR_SRC, BIN(0010,0010,0000,0000)); // ADCL, ADCR - i2c_write_reg(ADC_SRCBST_CTRL, BIN(0100,0100,0100,0000)); // disable all boost (default) + adac_write_word(AC101_ADDR, I2S1LCK_CTRL, BIN(1000,1000,0101,0000)); // Slave, BCLK=I2S/8,LRCK=32,16bits,I2Smode, Stereo + adac_write_word(AC101_ADDR, I2S1_SDOUT_CTRL, BIN(1100,0000,0000,0000)); // I2S1ADC (R&L) + adac_write_word(AC101_ADDR, I2S1_SDIN_CTRL, BIN(1100,0000,0000,0000)); // IS21DAC (R&L) + adac_write_word(AC101_ADDR, I2S1_MXR_SRC, BIN(0010,0010,0000,0000)); // ADCL, ADCR + adac_write_word(AC101_ADDR, ADC_SRCBST_CTRL, BIN(0100,0100,0100,0000)); // disable all boost (default) #if ENABLE_ADC - i2c_write_reg(ADC_SRC, BIN(0000,0100,0000,1000)); // source=linein(R/L) - i2c_write_reg(ADC_DIG_CTRL, BIN(1000,0000,0000,0000)); // enable digital ADC - i2c_write_reg(ADC_ANA_CTRL, BIN(1011, 1011,0000,0000)); // enable analogue R/L, 0dB + adac_write_word(AC101_ADDR, ADC_SRC, BIN(0000,0100,0000,1000)); // source=linein(R/L) + adac_write_word(AC101_ADDR, ADC_DIG_CTRL, BIN(1000,0000,0000,0000)); // enable digital ADC + adac_write_word(AC101_ADDR, ADC_ANA_CTRL, BIN(1011, 1011,0000,0000)); // enable analogue R/L, 0dB #else - i2c_write_reg(ADC_SRC, BIN(0000,0000,0000,0000)); // source=none - i2c_write_reg(ADC_DIG_CTRL, BIN(0000,0000,0000,0000)); // disable digital ADC - i2c_write_reg(ADC_ANA_CTRL, BIN(0011, 0011,0000,0000)); // disable analogue R/L, 0dB + adac_write_word(AC101_ADDR, ADC_SRC, BIN(0000,0000,0000,0000)); // source=none + adac_write_word(AC101_ADDR, ADC_DIG_CTRL, BIN(0000,0000,0000,0000)); // disable digital ADC + adac_write_word(AC101_ADDR, ADC_ANA_CTRL, BIN(0011, 0011,0000,0000)); // disable analogue R/L, 0dB #endif //Path Configuration - i2c_write_reg(DAC_MXR_SRC, BIN(1000,1000,0000,0000)); // DAC from I2S - i2c_write_reg(DAC_DIG_CTRL, BIN(1000,0000,0000,0000)); // enable DAC - i2c_write_reg(OMIXER_DACA_CTRL, BIN(1111,0000,0000,0000)); // enable DAC/Analogue (see note on offset removal and PA) - i2c_write_reg(OMIXER_DACA_CTRL, BIN(1111,1111,0000,0000)); // this toggle is needed for headphone PA offset + adac_write_word(AC101_ADDR, DAC_MXR_SRC, BIN(1000,1000,0000,0000)); // DAC from I2S + adac_write_word(AC101_ADDR, DAC_DIG_CTRL, BIN(1000,0000,0000,0000)); // enable DAC + adac_write_word(AC101_ADDR, OMIXER_DACA_CTRL, BIN(1111,0000,0000,0000)); // enable DAC/Analogue (see note on offset removal and PA) + adac_write_word(AC101_ADDR, OMIXER_DACA_CTRL, BIN(1111,1111,0000,0000)); // this toggle is needed for headphone PA offset #if ENABLE_ADC - i2c_write_reg(OMIXER_SR, BIN(0000,0001,0000,0010)); // source=DAC(R/L) (are DACR and DACL really inverted in bitmap?) + adac_write_word(AC101_ADDR, OMIXER_SR, BIN(0000,0001,0000,0010)); // source=DAC(R/L) (are DACR and DACL really inverted in bitmap?) #else - i2c_write_reg(OMIXER_SR, BIN(0000,0101,0000,1010)); // source=DAC(R/L) and LINEIN(R/L) + adac_write_word(AC101_ADDR, OMIXER_SR, BIN(0000,0101,0000,1010)); // source=DAC(R/L) and LINEIN(R/L) #endif // enable earphone & speaker - i2c_write_reg(SPKOUT_CTRL, 0x0220); - i2c_write_reg(HPOUT_CTRL, 0xf801); + adac_write_word(AC101_ADDR, SPKOUT_CTRL, 0x0220); + adac_write_word(AC101_ADDR, HPOUT_CTRL, 0xf801); // set gain for speaker and earphone ac101_set_spk_volume(100); ac101_set_earph_volume(100); - ESP_LOGI(TAG, "AC101 uses I2C sda:%d, scl:%d", i2c_config.sda_io_num, i2c_config.scl_io_num); - - return (res == ESP_OK); + return true; } -/**************************************************************************************** - * init - */ -static void deinit(void) { - i2c_driver_delete(i2c_port); -} - /**************************************************************************************** * change volume */ @@ -190,9 +156,9 @@ static void power(adac_power_e mode) { * speaker */ static void speaker(bool active) { - uint16_t value = i2c_read_reg(SPKOUT_CTRL); - if (active) i2c_write_reg(SPKOUT_CTRL, value | SPKOUT_EN); - else i2c_write_reg(SPKOUT_CTRL, value & ~SPKOUT_EN); + uint16_t value = adac_read_word(AC101_ADDR, SPKOUT_CTRL); + if (active) adac_write_word(AC101_ADDR, SPKOUT_CTRL, value | SPKOUT_EN); + else adac_write_word(AC101_ADDR, SPKOUT_CTRL, value & ~SPKOUT_EN); } /**************************************************************************************** @@ -200,51 +166,11 @@ static void speaker(bool active) { */ static void headset(bool active) { // there might be aneed to toggle OMIXER_DACA_CTRL 11:8, not sure - uint16_t value = i2c_read_reg(HPOUT_CTRL); - if (active) i2c_write_reg(HPOUT_CTRL, value | EAROUT_EN); - else i2c_write_reg(HPOUT_CTRL, value & ~EAROUT_EN); + uint16_t value = adac_read_word(AC101_ADDR, HPOUT_CTRL); + if (active) adac_write_word(AC101_ADDR, HPOUT_CTRL, value | EAROUT_EN); + else adac_write_word(AC101_ADDR, HPOUT_CTRL, value & ~EAROUT_EN); } -/**************************************************************************************** - * - */ -static esp_err_t i2c_write_reg(uint8_t reg, uint16_t val) -{ - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - esp_err_t ret =0; - uint8_t send_buff[4]; - send_buff[0] = (AC101_ADDR << 1); - send_buff[1] = reg; - send_buff[2] = (val>>8) & 0xff; - send_buff[3] = val & 0xff; - ret |= i2c_master_start(cmd); - ret |= i2c_master_write(cmd, send_buff, 4, ACK_CHECK_EN); - ret |= i2c_master_stop(cmd); - ret |= i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_RATE_MS); - i2c_cmd_link_delete(cmd); - return ret; -} - -/**************************************************************************************** - * - */ -static uint16_t i2c_read_reg(uint8_t reg) { - uint8_t data[2] = { 0 }; - - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, ( AC101_ADDR << 1 ) | WRITE_BIT, ACK_CHECK_EN); - i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, ( AC101_ADDR << 1 ) | READ_BIT, ACK_CHECK_EN); //check or not - i2c_master_read(cmd, data, 2, ACK_VAL); - i2c_master_stop(cmd); - i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_RATE_MS); - i2c_cmd_link_delete(cmd); - - return (data[0] << 8) + data[1];; -} - /**************************************************************************************** * */ @@ -264,7 +190,7 @@ void set_sample_rate(int rate) { ESP_LOGW(TAG, "Unknown sample rate %hu", rate); rate = SAMPLE_RATE_44100; } - i2c_write_reg(I2S_SR_CTRL, rate); + adac_write_word(AC101_ADDR, I2S_SR_CTRL, rate); } /**************************************************************************************** @@ -273,8 +199,8 @@ void set_sample_rate(int rate) { static void ac101_set_spk_volume(uint8_t volume) { uint16_t value = max(volume, 100); value = ((int) value * 0x1f) / 100; - value |= i2c_read_reg(SPKOUT_CTRL) & ~0x1f; - i2c_write_reg(SPKOUT_CTRL, value); + value |= adac_read_word(AC101_ADDR, SPKOUT_CTRL) & ~0x1f; + adac_write_word(AC101_ADDR, SPKOUT_CTRL, value); } /**************************************************************************************** @@ -283,8 +209,8 @@ static void ac101_set_spk_volume(uint8_t volume) { static void ac101_set_earph_volume(uint8_t volume) { uint16_t value = max(volume, 100); value = (((int) value * 0x3f) / 100) << 4; - value |= i2c_read_reg(HPOUT_CTRL) & ~(0x3f << 4); - i2c_write_reg(HPOUT_CTRL, value); + value |= adac_read_word(AC101_ADDR, HPOUT_CTRL) & ~(0x3f << 4); + adac_write_word(AC101_ADDR, HPOUT_CTRL, value); } #if 0 @@ -292,14 +218,14 @@ static void ac101_set_earph_volume(uint8_t volume) { * Get normalized (0..100) speaker volume */ static int ac101_get_spk_volume(void) { - return ((i2c_read_reg(SPKOUT_CTRL) & 0x1f) * 100) / 0x1f; + return ((adac_read_word(AC101_ADDR, SPKOUT_CTRL) & 0x1f) * 100) / 0x1f; } /**************************************************************************************** * Get normalized (0..100) earphone volume */ static int ac101_get_earph_volume(void) { - return (((i2c_read_reg(HPOUT_CTRL) >> 4) & 0x3f) * 100) / 0x3f; + return (((adac_read_word(AC101_ADDR, HPOUT_CTRL) >> 4) & 0x3f) * 100) / 0x3f; } /**************************************************************************************** @@ -308,7 +234,7 @@ static int ac101_get_earph_volume(void) { static void ac101_set_output_mixer_gain(ac_output_mixer_gain_t gain,ac_output_mixer_source_t source) { uint16_t regval,temp,clrbit; - regval = i2c_read_reg(OMIXER_BST1_CTRL); + regval = adac_read_word(AC101_ADDR, OMIXER_BST1_CTRL); switch(source){ case SRC_MIC1: temp = (gain&0x7) << 6; @@ -327,14 +253,15 @@ static void ac101_set_output_mixer_gain(ac_output_mixer_gain_t gain,ac_output_mi } regval &= clrbit; regval |= temp; - i2c_write_reg(OMIXER_BST1_CTRL,regval); + adac_write_word(AC101_ADDR, OMIXER_BST1_CTRL,regval); } /**************************************************************************************** * */ -static void ac101_deinit(void) { - i2c_write_reg(CHIP_AUDIO_RS, 0x123); //soft reset +static void deinit(void) { + adac_write_word(AC101_ADDR, CHIP_AUDIO_RS, 0x123); //soft reset + adac_deinit(); } /**************************************************************************************** @@ -342,11 +269,11 @@ static void ac101_deinit(void) { */ static void ac101_i2s_config_clock(ac_i2s_clock_t *cfg) { uint16_t regval=0; - regval = i2c_read_reg(I2S1LCK_CTRL); + regval = adac_read_word(AC101_ADDR, I2S1LCK_CTRL); regval &= 0xe03f; regval |= (cfg->bclk_div << 9); regval |= (cfg->lclk_div << 6); - i2c_write_reg(I2S1LCK_CTRL, regval); + adac_write_word(AC101_ADDR, I2S1LCK_CTRL, regval); } #endif @@ -356,21 +283,21 @@ static void ac101_i2s_config_clock(ac_i2s_clock_t *cfg) { */ static void ac101_start(ac_module_t mode) { if (mode == AC_MODULE_LINE) { - i2c_write_reg(0x51, 0x0408); - i2c_write_reg(0x40, 0x8000); - i2c_write_reg(0x50, 0x3bc0); + adac_write_word(AC101_ADDR, 0x51, 0x0408); + adac_write_word(AC101_ADDR, 0x40, 0x8000); + adac_write_word(AC101_ADDR, 0x50, 0x3bc0); } if (mode == AC_MODULE_ADC || mode == AC_MODULE_ADC_DAC || mode == AC_MODULE_LINE) { // I2S1_SDOUT_CTRL - // i2c_write_reg(PLL_CTRL2, 0x8120); - i2c_write_reg(0x04, 0x800c); - i2c_write_reg(0x05, 0x800c); - // res |= i2c_write_reg(0x06, 0x3000); + // adac_write_word(AC101_ADDR, PLL_CTRL2, 0x8120); + adac_write_word(AC101_ADDR, 0x04, 0x800c); + adac_write_word(AC101_ADDR, 0x05, 0x800c); + // res |= adac_write_word(AC101_ADDR, 0x06, 0x3000); } if (mode == AC_MODULE_DAC || mode == AC_MODULE_ADC_DAC || mode == AC_MODULE_LINE) { - uint16_t value = i2c_read_reg(PLL_CTRL2); + uint16_t value = adac_read_word(AC101_ADDR, PLL_CTRL2); value |= 0x8000; - i2c_write_reg(PLL_CTRL2, value); + adac_write_word(AC101_ADDR, PLL_CTRL2, value); } } @@ -378,8 +305,8 @@ static void ac101_start(ac_module_t mode) { * */ static void ac101_stop(void) { - uint16_t value = i2c_read_reg(PLL_CTRL2); + uint16_t value = adac_read_word(AC101_ADDR, PLL_CTRL2); value &= ~0x8000; - i2c_write_reg(PLL_CTRL2, value); + adac_write_word(AC101_ADDR, PLL_CTRL2, value); } diff --git a/components/squeezelite/adac.h b/components/squeezelite/adac.h index 369b0071..eeaa5d9c 100644 --- a/components/squeezelite/adac.h +++ b/components/squeezelite/adac.h @@ -11,6 +11,7 @@ #include "freertos/FreeRTOS.h" #include "driver/i2s.h" +#include "driver/i2c.h" typedef enum { ADAC_ON = 0, ADAC_STANDBY, ADAC_OFF } adac_power_e; @@ -28,3 +29,10 @@ extern const struct adac_s dac_tas57xx; extern const struct adac_s dac_tas5713; extern const struct adac_s dac_ac101; extern const struct adac_s dac_external; + +int adac_init(char *config, int i2c_port); +void adac_deinit(void); +esp_err_t adac_write_byte(int i2c_addr, uint8_t reg, uint8_t val); +esp_err_t adac_write_word(int i2c_addr, uint8_t reg, uint16_t val); +uint8_t adac_read_byte(int i2c_addr, uint8_t reg); +uint16_t adac_read_word(int i2c_addr, uint8_t reg); diff --git a/components/squeezelite/adac_core.c b/components/squeezelite/adac_core.c new file mode 100644 index 00000000..e83c2214 --- /dev/null +++ b/components/squeezelite/adac_core.c @@ -0,0 +1,164 @@ +/* + * Squeezelite for esp32 + * + * (c) Sebastien 2019 + * Philippe G. 2019, philippe_44@outlook.com + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + * + */ + +#include +#include +#include +#include +#include "driver/i2c.h" +#include "esp_log.h" +#include "adac.h" + +static const char TAG[] = "DAC core"; +static int i2c_port = -1; + +/**************************************************************************************** + * init + */ +int adac_init(char *config, int i2c_port_num) { + char *p; + int i2c_addr = 0; + i2c_port = i2c_port_num; + + // configure i2c + i2c_config_t i2c_config = { + .mode = I2C_MODE_MASTER, + .sda_io_num = -1, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_io_num = -1, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = 250000, + }; + + if ((p = strcasestr(config, "i2c")) != NULL) i2c_addr = atoi(strchr(p, '=') + 1); + if ((p = strcasestr(config, "sda")) != NULL) i2c_config.sda_io_num = atoi(strchr(p, '=') + 1); + if ((p = strcasestr(config, "scl")) != NULL) i2c_config.scl_io_num = atoi(strchr(p, '=') + 1); + + if (i2c_config.sda_io_num == -1 || i2c_config.scl_io_num == -1) { + ESP_LOGW(TAG, "DAC does not use i2c"); + return i2c_addr; + } + + ESP_LOGI(TAG, "DAC uses I2C port:%d, sda:%d, scl:%d", i2c_port, i2c_config.sda_io_num, i2c_config.scl_io_num); + + // we have an I2C configured + i2c_param_config(i2c_port, &i2c_config); + i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false); + + return i2c_addr; +} + +/**************************************************************************************** + * close + */ +void adac_deinit(void) { + if (i2c_port != -1) i2c_driver_delete(i2c_port); +} + +/**************************************************************************************** + * + */ +esp_err_t adac_write_byte(int i2c_addr,uint8_t reg, uint8_t val) { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + + i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK); + i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK); + i2c_master_write_byte(cmd, val, I2C_MASTER_NACK); + + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + + if (ret != ESP_OK) { + ESP_LOGW(TAG, "I2C write failed"); + } + + return ret; +} + +/**************************************************************************************** + * + */ +uint8_t adac_read_byte(int i2c_addr, uint8_t reg) { + uint8_t data = 255; + + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + + i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK); + i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK); + + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_READ, I2C_MASTER_NACK); + i2c_master_read_byte(cmd, &data, I2C_MASTER_NACK); + + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + + if (ret != ESP_OK) { + ESP_LOGW(TAG, "I2C read failed"); + } + + return data; +} + +/**************************************************************************************** + * + */ +uint16_t adac_read_word(int i2c_addr, uint8_t reg) { + uint8_t data[2] = { 255, 255 }; + + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + + i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK); + i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK); + + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_READ, I2C_MASTER_NACK); + i2c_master_read(cmd, data, 2, I2C_MASTER_NACK); + + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + + if (ret != ESP_OK) { + ESP_LOGW(TAG, "I2C read failed"); + } + + return (data[0] << 8) | data[1]; +} + +/**************************************************************************************** + * + */ +esp_err_t adac_write_word(int i2c_addr, uint8_t reg, uint16_t val) +{ + uint8_t data[] = { i2c_addr << 1, reg, + val >> 8, val & 0xff }; + + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + + i2c_master_write(cmd, data, 4, I2C_MASTER_NACK); + + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + + if (ret != ESP_OK) { + ESP_LOGW(TAG, "I2C write failed"); + } + + return ret; +} \ No newline at end of file diff --git a/components/squeezelite/external/dac_external.c b/components/squeezelite/external/dac_external.c index 60242a7d..0694ab51 100644 --- a/components/squeezelite/external/dac_external.c +++ b/components/squeezelite/external/dac_external.c @@ -20,7 +20,6 @@ static const char TAG[] = "DAC external"; -static void deinit(void) { } static void speaker(bool active) { } static void headset(bool active) { } static bool volume(unsigned left, unsigned right) { return false; } @@ -28,48 +27,30 @@ static void power(adac_power_e mode); static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config); static bool i2c_json_execute(char *set); -static esp_err_t i2c_write_reg(uint8_t reg, uint8_t val); -static uint8_t i2c_read_reg(uint8_t reg); -const struct adac_s dac_external = { "i2s", init, deinit, power, speaker, headset, volume }; -static int i2c_port, i2c_addr; +const struct adac_s dac_external = { "i2s", init, adac_deinit, power, speaker, headset, volume }; static cJSON *i2c_json; +static int i2c_addr; /**************************************************************************************** * init */ static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) { char *p; - i2c_port = i2c_port_num; - // configure i2c - i2c_config_t i2c_config = { - .mode = I2C_MODE_MASTER, - .sda_io_num = -1, - .sda_pullup_en = GPIO_PULLUP_ENABLE, - .scl_io_num = -1, - .scl_pullup_en = GPIO_PULLUP_ENABLE, - .master.clk_speed = 250000, - }; - - if ((p = strcasestr(config, "i2c")) != NULL) i2c_addr = atoi(strchr(p, '=') + 1); - if ((p = strcasestr(config, "sda")) != NULL) i2c_config.sda_io_num = atoi(strchr(p, '=') + 1); - if ((p = strcasestr(config, "scl")) != NULL) i2c_config.scl_io_num = atoi(strchr(p, '=') + 1); - + i2c_addr = adac_init(config, i2c_port_num); + if (!i2c_addr) return false; + + ESP_LOGI(TAG, "DAC on I2C @%d", i2c_addr); + p = config_alloc_get_str("dac_controlset", CONFIG_DAC_CONTROLSET, NULL); i2c_json = cJSON_Parse(p); - if (!i2c_addr || !i2c_json || i2c_config.sda_io_num == -1 || i2c_config.scl_io_num == -1) { + if (!i2c_json) { if (p) free(p); - ESP_LOGW(TAG, "No i2c controlset found"); + ESP_LOGW(TAG, "no i2c controlset found"); return true; } - - ESP_LOGI(TAG, "DAC uses I2C @%d with sda:%d, scl:%d", i2c_addr, i2c_config.sda_io_num, i2c_config.scl_io_num); - - // we have an I2C configured - i2c_param_config(i2c_port, &i2c_config); - i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false); if (!i2c_json_execute("init")) { ESP_LOGE(TAG, "could not intialize DAC"); @@ -105,70 +86,17 @@ bool i2c_json_execute(char *set) { if (!reg || !val) continue; if (!mode) { - i2c_write_reg(reg->valueint, val->valueint); + adac_write_byte(i2c_addr, reg->valueint, val->valueint); } else if (!strcasecmp(mode->valuestring, "or")) { - uint8_t data = i2c_read_reg(reg->valueint); + uint8_t data = adac_read_byte(i2c_addr,reg->valueint); data |= (uint8_t) val->valueint; - i2c_write_reg(reg->valueint, data); + adac_write_byte(i2c_addr, reg->valueint, data); } else if (!strcasecmp(mode->valuestring, "and")) { - uint8_t data = i2c_read_reg(reg->valueint); + uint8_t data = adac_read_byte(i2c_addr, reg->valueint); data &= (uint8_t) val->valueint; - i2c_write_reg(reg->valueint, data); + adac_write_byte(i2c_addr, reg->valueint, data); } } return true; } - -/**************************************************************************************** - * - */ -static esp_err_t i2c_write_reg(uint8_t reg, uint8_t val) { - esp_err_t ret; - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - - i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK); - i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK); - i2c_master_write_byte(cmd, val, I2C_MASTER_NACK); - - i2c_master_stop(cmd); - ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS); - i2c_cmd_link_delete(cmd); - - if (ret != ESP_OK) { - ESP_LOGW(TAG, "I2C write failed"); - } - - return ret; -} - -/**************************************************************************************** - * - */ -static uint8_t i2c_read_reg(uint8_t reg) { - esp_err_t ret; - uint8_t data = 0; - - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - - i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK); - i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK); - - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_READ, I2C_MASTER_NACK); - i2c_master_read_byte(cmd, &data, I2C_MASTER_NACK); - - i2c_master_stop(cmd); - ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS); - i2c_cmd_link_delete(cmd); - - if (ret != ESP_OK) { - ESP_LOGW(TAG, "I2C read failed"); - } - - return data; -} - - diff --git a/components/squeezelite/helix-aac.c b/components/squeezelite/helix-aac.c index 723db9ff..f2a48287 100644 --- a/components/squeezelite/helix-aac.c +++ b/components/squeezelite/helix-aac.c @@ -158,10 +158,11 @@ static int read_mp4_header(unsigned long *samplerate_p, unsigned char *channels_ info.sampRateCore = rates[info.sampRateCore]; info.nChans = (*ptr++ & 0x7f) >> 3; *channels_p = info.nChans; - *samplerate_p = info.sampRateCore; if (desc_len > 2 && ((ptr[0] << 3) | (ptr[1] >> 5)) == 0x2b7 && (ptr[1] & 0x1f) == 0x05 && (ptr[2] & 0x80)) { - LOG_WARN("AAC SBR mode activated => high CPU consumption (please proxy)"); *samplerate_p = rates[(ptr[2] & 0x78) >> 3]; + LOG_WARN("AAC SBR mode activated => high CPU consumption expected, please use LMS proxy to mitigate"); + } else { + *samplerate_p = info.sampRateCore; } HAAC(a, SetRawBlockParams, a->hAac, 0, &info); LOG_DEBUG("playable aac track: %u (p:%x, r:%d, c:%d)", trak, info.profile, info.sampRateCore, info.nChans); diff --git a/components/squeezelite/tas57xx/dac_5713.c b/components/squeezelite/tas57xx/dac_5713.c index 135fa6f7..7bc77f9d 100644 --- a/components/squeezelite/tas57xx/dac_5713.c +++ b/components/squeezelite/tas57xx/dac_5713.c @@ -20,7 +20,7 @@ #include "adac.h" #define ARRAY_SIZE(array) (sizeof(array) / sizeof(*array)) -#define TAS5713 0x36 /* i2c address of TAS5713 */ +#define TAS5713 (0x36 >> 1) /* i2c address of TAS5713 */ // TAS5713 I2C-bus register addresses @@ -40,13 +40,12 @@ static const char TAG[] = "TAS5713"; static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config); -static void deinit(void); static void speaker(bool active) { }; static void headset(bool active) { } ; static bool volume(unsigned left, unsigned right); static void power(adac_power_e mode) { }; -const struct adac_s dac_tas5713 = {"TAS5713", init, deinit, power, speaker, headset, volume}; +const struct adac_s dac_tas5713 = {"TAS5713", init, adac_deinit, power, speaker, headset, volume}; struct tas5713_cmd_s { uint8_t reg; @@ -63,53 +62,30 @@ typedef enum { TAS57_VOLUME } dac_cmd_e; -static int i2c_port; - -static void tas5713_set(uint8_t reg, uint8_t val); -static uint8_t tas5713_get(uint8_t reg); - /**************************************************************************************** * init */ -static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) { - char *p; - i2c_port = i2c_port_num; - - // configure i2c - i2c_config_t i2c_config = { - .mode = I2C_MODE_MASTER, - .sda_io_num = -1, - .sda_pullup_en = GPIO_PULLUP_ENABLE, - .scl_io_num = -1, - .scl_pullup_en = GPIO_PULLUP_ENABLE, - .master.clk_speed = 250000, - }; - - if ((p = strcasestr(config, "sda")) != NULL) i2c_config.sda_io_num = atoi(strchr(p, '=') + 1); - if ((p = strcasestr(config, "scl")) != NULL) i2c_config.scl_io_num = atoi(strchr(p, '=') + 1); - - i2c_param_config(i2c_port, &i2c_config); - esp_err_t res = i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false); - - /* find if there is a tas5713 attached. Reg 0 should read non-zero if so */ - if (!tas5713_get(0x00)) { +static bool init(char *config, int i2c_port, i2s_config_t *i2s_config) { + /* find if there is a tas5713 attached. Reg 0 should read non-zero but not 255 if so */ + adac_init(config, i2c_port); + if (adac_read_byte(TAS5713, 0x00) == 255) { ESP_LOGW(TAG, "No TAS5713 detected"); - i2c_driver_delete(i2c_port); + adac_deinit(); return 0; } - ESP_LOGI(TAG, "TAS5713 uses I2C sda:%d, scl:%d", i2c_config.sda_io_num, i2c_config.scl_io_num); + ESP_LOGI(TAG, "TAS5713 found"); /* do the init sequence */ - tas5713_set(TAS5713_OSC_TRIM, 0x00); /* a delay is required after this */ + esp_err_t res = adac_write_byte(TAS5713, TAS5713_OSC_TRIM, 0x00); /* a delay is required after this */ vTaskDelay(50 / portTICK_PERIOD_MS); - tas5713_set(TAS5713_SERIAL_DATA_INTERFACE, 0x03); /* I2S LJ 16 bit */ - tas5713_set(TAS5713_SYSTEM_CTRL2, 0x00); /* exit all channel shutdown */ - tas5713_set(TAS5713_SOFT_MUTE, 0x00); /* unmute */ - tas5713_set(TAS5713_VOL_MASTER, 0x20); - tas5713_set(TAS5713_VOL_CH1, 0x30); - tas5713_set(TAS5713_VOL_CH2, 0x30); - tas5713_set(TAS5713_VOL_HEADPHONE, 0xFF); + res |= adac_write_byte(TAS5713, TAS5713_SERIAL_DATA_INTERFACE, 0x03); /* I2S LJ 16 bit */ + res |= adac_write_byte(TAS5713, TAS5713_SYSTEM_CTRL2, 0x00); /* exit all channel shutdown */ + res |= adac_write_byte(TAS5713, TAS5713_SOFT_MUTE, 0x00); /* unmute */ + res |= adac_write_byte(TAS5713, TAS5713_VOL_MASTER, 0x20); + res |= adac_write_byte(TAS5713, TAS5713_VOL_CH1, 0x30); + res |= adac_write_byte(TAS5713, TAS5713_VOL_CH2, 0x30); + res |= adac_write_byte(TAS5713, TAS5713_VOL_HEADPHONE, 0xFF); /* The tas5713 typically has the mclk connected to the sclk. In this configuration, mclk must be a multiple of the sclk. The lowest workable @@ -126,70 +102,9 @@ static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) { return true; } -/**************************************************************************************** - * init - */ -static void deinit(void) { - i2c_driver_delete(i2c_port); -} - /**************************************************************************************** * change volume */ static bool volume(unsigned left, unsigned right) { return false; } - - -/**************************************************************************************** - * DAC specific commands - */ -void tas5713_set(uint8_t reg, uint8_t val) { - esp_err_t ret = ESP_OK; - - ESP_LOGI(TAG,"TAS5713 send %x %x", reg, val); - i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); - - i2c_master_start(i2c_cmd); - i2c_master_write_byte(i2c_cmd, - TAS5713 | I2C_MASTER_WRITE, - I2C_MASTER_NACK); - i2c_master_write_byte(i2c_cmd, reg, I2C_MASTER_NACK); - i2c_master_write_byte(i2c_cmd, val, I2C_MASTER_NACK); - i2c_master_stop(i2c_cmd); - ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_RATE_MS); - - i2c_cmd_link_delete(i2c_cmd); - - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Could not send command to TAS5713 %d", ret); - } -} - -/************************************************************************* - * Read from i2c for the tas5713. This doubles as tas5713 detect. This function - * returns zero on error, so read register 0x00 for tas detect, which will be - * non-zero in this application. - */ -static uint8_t tas5713_get(uint8_t reg) { - int ret; - uint8_t data = 0; - i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); - - i2c_master_start(i2c_cmd); - i2c_master_write_byte(i2c_cmd, TAS5713 | I2C_MASTER_WRITE, I2C_MASTER_NACK); - i2c_master_write_byte(i2c_cmd, reg, I2C_MASTER_NACK); - - i2c_master_start(i2c_cmd); - i2c_master_write_byte(i2c_cmd, TAS5713 | I2C_MASTER_READ, I2C_MASTER_NACK); - i2c_master_read_byte(i2c_cmd, &data, I2C_MASTER_NACK); - - i2c_master_stop(i2c_cmd); - ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_RATE_MS); - i2c_cmd_link_delete(i2c_cmd); - - if (ret == ESP_OK) { - ESP_LOGI(TAG,"TAS5713 reg 0x%x is 0x%x", reg, data); - } - return data; -} diff --git a/components/squeezelite/tas57xx/dac_57xx.c b/components/squeezelite/tas57xx/dac_57xx.c index aa0fac3a..b585ede4 100644 --- a/components/squeezelite/tas57xx/dac_57xx.c +++ b/components/squeezelite/tas57xx/dac_57xx.c @@ -18,19 +18,18 @@ #include "esp_log.h" #include "adac.h" -#define TAS575x 0x98 -#define TAS578x 0x90 +#define TAS575x (0x98 >> 1) +#define TAS578x (0x90 >> 1) static const char TAG[] = "TAS575x/8x"; static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config); -static void deinit(void); static void speaker(bool active); static void headset(bool active); static bool volume(unsigned left, unsigned right); static void power(adac_power_e mode); -const struct adac_s dac_tas57xx = { "TAS57xx", init, deinit, power, speaker, headset, volume }; +const struct adac_s dac_tas57xx = { "TAS57xx", init, adac_deinit, power, speaker, headset, volume }; struct tas57xx_cmd_s { uint8_t reg; @@ -60,7 +59,7 @@ static const struct tas57xx_cmd_s tas57xx_cmd[] = { }; static uint8_t tas57_addr; -static int i2c_port; + int i2c_port_x; static void dac_cmd(dac_cmd_e cmd, ...); static int tas57_detect(void); @@ -68,32 +67,15 @@ static int tas57_detect(void); /**************************************************************************************** * init */ -static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) { - char *p; - i2c_port = i2c_port_num; - - // configure i2c - i2c_config_t i2c_config = { - .mode = I2C_MODE_MASTER, - .sda_io_num = -1, - .sda_pullup_en = GPIO_PULLUP_ENABLE, - .scl_io_num = -1, - .scl_pullup_en = GPIO_PULLUP_ENABLE, - .master.clk_speed = 250000, - }; - - if ((p = strcasestr(config, "sda")) != NULL) i2c_config.sda_io_num = atoi(strchr(p, '=') + 1); - if ((p = strcasestr(config, "scl")) != NULL) i2c_config.scl_io_num = atoi(strchr(p, '=') + 1); - - i2c_param_config(i2c_port, &i2c_config); - i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false); - +static bool init(char *config, int i2c_port, i2s_config_t *i2s_config) { // find which TAS we are using (if any) +i2c_port_x = i2c_port; + adac_init(config, i2c_port); tas57_addr = tas57_detect(); - + if (!tas57_addr) { ESP_LOGW(TAG, "No TAS57xx detected"); - i2c_driver_delete(i2c_port); + adac_deinit(); return false; } @@ -101,7 +83,7 @@ static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) { for (int i = 0; tas57xx_init_sequence[i].reg != 0xff; i++) { i2c_master_start(i2c_cmd); - i2c_master_write_byte(i2c_cmd, tas57_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK); + i2c_master_write_byte(i2c_cmd, (tas57_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK); i2c_master_write_byte(i2c_cmd, tas57xx_init_sequence[i].reg, I2C_MASTER_NACK); i2c_master_write_byte(i2c_cmd, tas57xx_init_sequence[i].value, I2C_MASTER_NACK); ESP_LOGD(TAG, "i2c write %x at %u", tas57xx_init_sequence[i].reg, tas57xx_init_sequence[i].value); @@ -110,8 +92,6 @@ static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) { i2c_master_stop(i2c_cmd); esp_err_t res = i2c_master_cmd_begin(i2c_port, i2c_cmd, 500 / portTICK_RATE_MS); i2c_cmd_link_delete(i2c_cmd); - - ESP_LOGI(TAG, "TAS57xx uses I2C sda:%d, scl:%d", i2c_config.sda_io_num, i2c_config.scl_io_num); if (res != ESP_OK) { ESP_LOGE(TAG, "could not intialize TAS57xx %d", res); @@ -121,13 +101,6 @@ static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) { return true; } -/**************************************************************************************** - * init - */ -static void deinit(void) { - i2c_driver_delete(i2c_port); -} - /**************************************************************************************** * change volume */ @@ -176,25 +149,17 @@ void dac_cmd(dac_cmd_e cmd, ...) { esp_err_t ret = ESP_OK; va_start(args, cmd); - i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); switch(cmd) { case TAS57_VOLUME: ESP_LOGE(TAG, "DAC volume not handled yet"); break; default: - i2c_master_start(i2c_cmd); - i2c_master_write_byte(i2c_cmd, tas57_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK); - i2c_master_write_byte(i2c_cmd, tas57xx_cmd[cmd].reg, I2C_MASTER_NACK); - i2c_master_write_byte(i2c_cmd, tas57xx_cmd[cmd].value, I2C_MASTER_NACK); - i2c_master_stop(i2c_cmd); - ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_RATE_MS); + ret = adac_write_byte(tas57_addr, tas57xx_cmd[cmd].reg, tas57xx_cmd[cmd].value); } - i2c_cmd_link_delete(i2c_cmd); - - if (ret != ESP_OK) { - ESP_LOGE(TAG, "could not intialize TAS57xx %d", ret); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "could not use TAS57xx %d", ret); } va_end(args); @@ -204,25 +169,10 @@ void dac_cmd(dac_cmd_e cmd, ...) { * TAS57 detection */ static int tas57_detect(void) { - uint8_t data, addr[] = {TAS578x, TAS575x}; - int ret; + uint8_t addr[] = {TAS578x, TAS575x}; for (int i = 0; i < sizeof(addr); i++) { - i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); - - i2c_master_start(i2c_cmd); - i2c_master_write_byte(i2c_cmd, addr[i] | I2C_MASTER_WRITE, I2C_MASTER_NACK); - i2c_master_write_byte(i2c_cmd, 00, I2C_MASTER_NACK); - - i2c_master_start(i2c_cmd); - i2c_master_write_byte(i2c_cmd, addr[i] | I2C_MASTER_READ, I2C_MASTER_NACK); - i2c_master_read_byte(i2c_cmd, &data, I2C_MASTER_NACK); - - i2c_master_stop(i2c_cmd); - ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_RATE_MS); - i2c_cmd_link_delete(i2c_cmd); - - if (ret == ESP_OK) { + if (adac_read_byte(addr[i], 0) != 255) { ESP_LOGI(TAG, "Detected TAS @0x%x", addr[i]); return addr[i]; }