Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
qtmoko/devices/gta04/server/neohardware.cpp
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
320 lines (269 sloc)
10 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /**************************************************************************** | |
| ** | |
| ** This file is part of the Qt Extended Opensource Package. | |
| ** | |
| ** Copyright (C) 2009 Trolltech ASA. | |
| ** Copyright (C) 2012 Radek Polak. | |
| ** | |
| ** Contact: Qt Extended Information (info@qtextended.org) | |
| ** | |
| ** This file may be used under the terms of the GNU General Public License | |
| ** version 2.0 as published by the Free Software Foundation and appearing | |
| ** in the file LICENSE.GPL included in the packaging of this file. | |
| ** | |
| ** Please review the following information to ensure GNU General Public | |
| ** Licensing requirements will be met: | |
| ** http://www.fsf.org/licensing/licenses/info/GPLv2.html. | |
| ** | |
| ** | |
| ****************************************************************************/ | |
| #include "neohardware.h" | |
| #include <QSocketNotifier> | |
| #include <QTimer> | |
| #include <QLabel> | |
| #include <QDesktopWidget> | |
| #include <QProcess> | |
| #include <QSettings> | |
| #include <QPowerSourceProvider> | |
| #include <qcontentset.h> | |
| #include <qtopiaapplication.h> | |
| #include <qtopialog.h> | |
| #include <qtopiaipcadaptor.h> | |
| #include <qbootsourceaccessory.h> | |
| #include <qtopiaipcenvelope.h> | |
| #include <qtopiaserverapplication.h> | |
| #include <fcntl.h> | |
| #include <errno.h> | |
| #include <unistd.h> | |
| #include <linux/input.h> | |
| #include <sys/ioctl.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <ctype.h> | |
| #include <sys/un.h> | |
| #include <sys/socket.h> | |
| #include <linux/types.h> | |
| #include <linux/netlink.h> | |
| #include <QTcpSocket> | |
| #include <QtDebug> | |
| QTOPIA_TASK(NeoHardware, NeoHardware); | |
| // Setup netlink socket for watching usb cable and battery events | |
| static int openNetlink() | |
| { | |
| struct sockaddr_nl snl; | |
| memset(&snl, 0x00, sizeof(struct sockaddr_nl)); | |
| snl.nl_family = AF_NETLINK; | |
| snl.nl_pid = getpid(); | |
| snl.nl_groups = 1; | |
| snl.nl_groups = 0x1; | |
| int fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); | |
| if (fd == -1) { | |
| qLog(Hardware) << "error getting uevent socket: " << strerror(errno); | |
| return -1; | |
| } | |
| if (bind(fd, (struct sockaddr *)&snl, sizeof(struct sockaddr_nl)) < 0) { | |
| qLog(Hardware) << "uevent bind failed: " << strerror(errno); | |
| return -1; | |
| } | |
| return fd; | |
| } | |
| NeoHardware::NeoHardware() | |
| : | |
| ac(QPowerSource::Wall, "PrimaryAC", this) | |
| , battery(QPowerSource::Battery, "NeoBattery", this) | |
| , batteryVso("/UI/Battery", this) | |
| , vsoPortableHandsfree("/Hardware/Accessories/PortableHandsfree") | |
| , chargeLog("/var/log/charging") | |
| , lastLogDt() | |
| , ueventSocket(this) | |
| , timer(this) | |
| , chargingLogInterval(300) | |
| , maxChargeCurrent(-1) | |
| { | |
| qLog(Hardware) << "gta04 hardware"; | |
| int netlinkFd = openNetlink(); | |
| if (netlinkFd >= 0) { | |
| ueventSocket.setSocketDescriptor(netlinkFd); | |
| connect(&ueventSocket, SIGNAL(readyRead()), this, SLOT(uevent())); | |
| } | |
| hasSmartBattery = | |
| QFile::exists("/sys/class/power_supply/bq27000-battery/status"); | |
| connect(&timer, SIGNAL(timeout()), this, SLOT(updateStatus())); | |
| timer.start(30 * 1000); | |
| QTimer::singleShot(1, this, SLOT(updateStatus())); | |
| adaptor = new QtopiaIpcAdaptor("QPE/NeoHardware"); | |
| audioMgr = new QtopiaIpcAdaptor("QPE/AudioStateManager", this); | |
| QtopiaIpcAdaptor::connect(adaptor, MESSAGE(headphonesInserted(bool)), | |
| this, SLOT(headphonesInserted(bool))); | |
| // Y sets usb charging limit to 600mA which can be too high and causes | |
| // charging voltage drops. We will set limit manually to prevent this. | |
| qWriteFile("/sys/module/twl4030_charger/parameters/allow_usb", "N"); | |
| QSettings cfg("Trolltech", "qpe"); | |
| cfg.beginGroup("Charging"); | |
| chargingLogInterval = cfg.value("LogInterval", 300).toInt(); | |
| } | |
| NeoHardware::~NeoHardware() | |
| { | |
| } | |
| void NeoHardware::setMaxChargeCurrent(int newValue) | |
| { | |
| if (newValue < 100000) | |
| newValue = 100000; | |
| if (newValue > MAX_CURRENT) | |
| newValue = MAX_CURRENT; | |
| if (maxChargeCurrent == newValue) | |
| return; | |
| char buf[7]; | |
| maxChargeCurrent = newValue; | |
| sprintf(buf, "%d", maxChargeCurrent); | |
| qWriteFile("/sys/class/power_supply/twl4030_usb/max_current", buf); | |
| } | |
| // Parse uevent string and return given attribute value. Example uevent file: | |
| // POWER_SUPPLY_NAME=bq27000-battery | |
| // POWER_SUPPLY_STATUS=Full | |
| // POWER_SUPPLY_PRESENT=1 | |
| // POWER_SUPPLY_VOLTAGE_NOW=65535000 | |
| // POWER_SUPPLY_CURRENT_NOW=-11697997 | |
| // POWER_SUPPLY_CAPACITY_LEVEL=Full | |
| // POWER_SUPPLY_TEMP=161106 | |
| // POWER_SUPPLY_TECHNOLOGY=Li-ion | |
| // POWER_SUPPLY_CHARGE_NOW=11697997 | |
| // POWER_SUPPLY_CHARGE_FULL_DESIGN=11652480 | |
| // POWER_SUPPLY_CYCLE_COUNT=65535 | |
| // | |
| // Btw this is uevent file for case of uncalibrated battery. | |
| // | |
| // The argument name must contain even the '=' char | |
| static QByteArray getAttr(const char *name, QByteArray & uevent) | |
| { | |
| int index = uevent.indexOf(name); | |
| if (index < 0) | |
| return QByteArray(); | |
| index += strlen(name); | |
| int end = uevent.indexOf('\n', index + 1); | |
| return uevent.mid(index, end - index); | |
| } | |
| static int getIntAttr(const char *name, QByteArray & uevent) | |
| { | |
| QByteArray str = getAttr(name, uevent); | |
| if (str.isEmpty()) | |
| return -1; | |
| return str.toInt(); | |
| } | |
| void NeoHardware::logCharge(QDateTime now, int chargeNow) | |
| { | |
| if (lastLogDt.secsTo(now) < chargingLogInterval) | |
| return; | |
| lastLogDt = now; | |
| if (!chargeLog.exists()) | |
| return; | |
| QString newEntry = | |
| now.toString("yyyy-MM-dd hh:mm:ss") + "\t" + | |
| QString::number(chargeNow) + "\n"; | |
| if (!chargeLog.open(QIODevice::WriteOnly | QIODevice::Append)) | |
| return; | |
| chargeLog.write(newEntry.toLatin1().constData()); | |
| chargeLog.close(); | |
| // When user selects fast updates, use timer to update status because | |
| // uevent does not trigger that fast. | |
| if (chargingLogInterval < 300) | |
| QTimer::singleShot(1000 * chargingLogInterval, this, SLOT(updateStatus())); | |
| } | |
| void NeoHardware::updateStatus() | |
| { | |
| QDateTime now = QDateTime::currentDateTime(); | |
| // Determine charging via USB | |
| QByteArray twlVbus = | |
| qReadFile("/sys/bus/platform/devices/twl4030_usb/vbus"); | |
| bool chargerOn = twlVbus.contains("on"); | |
| if (chargerOn) | |
| ac.setAvailability(QPowerSource::Available); | |
| else | |
| ac.setAvailability(QPowerSource::NotAvailable); | |
| battery.setCharging(chargerOn); | |
| // Update battery status | |
| QByteArray uevent = | |
| qReadFile("/sys/class/power_supply/bq27000-battery/uevent"); | |
| int timeToEmpty = getIntAttr("POWER_SUPPLY_TIME_TO_EMPTY_NOW=", uevent); | |
| if (timeToEmpty >= 0) | |
| battery.setTimeRemaining(timeToEmpty / 60); | |
| int capacity = getIntAttr("POWER_SUPPLY_CAPACITY=", uevent); | |
| int chargeNow = getIntAttr("POWER_SUPPLY_CHARGE_NOW=", uevent); | |
| if (capacity < 0) { | |
| // Handle uncalibrated battery - it does not have POWER_SUPPLY_CAPACITY | |
| // and we compute capacity from charge_now and charge_full_design. | |
| // http://lists.goldelico.com/pipermail/gta04-owner/2013-February/003903.html | |
| int chargeFullDesign = | |
| getIntAttr("POWER_SUPPLY_CHARGE_FULL_DESIGN=", uevent); | |
| capacity = (chargeNow * 100) / chargeFullDesign; | |
| } | |
| if (capacity > 0) | |
| battery.setCharge(capacity); | |
| int currentNow = getIntAttr("POWER_SUPPLY_CURRENT_NOW=", uevent); | |
| batteryVso.setAttribute("current_now", QString::number(currentNow / 1000)); | |
| // Now we will try to set max_current so that phone charges reliably. | |
| int chargerVoltage = -1; | |
| if (chargerOn) { | |
| if (maxChargeCurrent < 0) | |
| setMaxChargeCurrent(INIT_CURRENT); | |
| QByteArray chargerUevent = | |
| qReadFile("/sys/class/power_supply/twl4030_usb/uevent"); | |
| chargerVoltage = getIntAttr("POWER_SUPPLY_VOLTAGE_NOW=", chargerUevent); | |
| // Warn if charging voltage drops too low | |
| if (chargerVoltage < 4500000 && chargerVoltage > 0) | |
| qWarning() << "Charging voltage dropped to " << chargerVoltage << | |
| ", charging might not restart when battery gets low"; | |
| // Battery discharges | |
| if (currentNow > 0 || capacity <= 90) { | |
| // Increase charging current until 600mA, but make sure that | |
| // voltage wont drop under 4.5V, for more info see: | |
| // http://lists.goldelico.com/pipermail/gta04-owner/2013-September/004963.html | |
| if (chargerVoltage > 4600000 && chargerVoltage != oldChargerVoltage) { | |
| setMaxChargeCurrent(maxChargeCurrent + CURRENT_PLUS); | |
| QTimer::singleShot(200, this, SLOT(updateStatus())); | |
| } | |
| } else if (currentNow < -50000 && oldChargeNow != chargeNow) { // Battery is finishing charging | |
| setMaxChargeCurrent(maxChargeCurrent + currentNow / 2); // slow charging down under 50mA | |
| } | |
| oldChargerVoltage = chargerVoltage; | |
| } else if (maxChargeCurrent > INIT_CURRENT) { | |
| maxChargeCurrent = -1; | |
| } | |
| oldChargeNow = chargeNow; | |
| // Charging log | |
| logCharge(now, chargeNow); | |
| } | |
| #define UEVENT_BUFFER_SIZE 1024 | |
| void NeoHardware::uevent() | |
| { | |
| char buffer[1024]; | |
| if (ueventSocket.read(buffer, UEVENT_BUFFER_SIZE) <= 0) { | |
| return; | |
| } | |
| if (strstr(buffer, "twl4030") || strstr(buffer, "bq27000-battery")) { | |
| QTimer::singleShot(1000, this, SLOT(updateStatus())); // vbus updates < 1s | |
| QTimer::singleShot(2000, this, SLOT(updateStatus())); // but sometimes needs 2s when changing on->off | |
| QTimer::singleShot(10000, this, SLOT(updateStatus())); // battery charging needs 10s | |
| QTimer::singleShot(20000, this, SLOT(updateStatus())); // try also after 20s in case status at 10s was not yet correct | |
| } | |
| } | |
| void NeoHardware::shutdownRequested() | |
| { | |
| qLog(PowerManagement) << __PRETTY_FUNCTION__; | |
| QtopiaServerApplication::instance()-> | |
| shutdown(QtopiaServerApplication::ShutdownSystem); | |
| } | |
| void NeoHardware::headphonesInserted(bool b) | |
| { | |
| qLog(Hardware) << __PRETTY_FUNCTION__ << b; | |
| vsoPortableHandsfree.setAttribute("Present", b); | |
| vsoPortableHandsfree.sync(); | |
| if (b) { | |
| QByteArray mode("Headphone"); | |
| audioMgr->send("setProfile(QByteArray)", mode); | |
| } else { | |
| QByteArray mode("MediaSpeaker"); | |
| audioMgr->send("setProfile(QByteArray)", mode); | |
| } | |
| } |