You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koreader/frontend/device/kindle/powerd.lua

323 lines
12 KiB
Lua

local BasePowerD = require("device/generic/powerd")
local UIManager
local WakeupMgr = require("device/wakeupmgr")
local logger = require("logger")
local ffiUtil = require("ffi/util")
-- liblipclua, see require below
local KindlePowerD = BasePowerD:new{
fl_min = 0, fl_max = 24,
fl_warmth_min = 0, fl_warmth_max = 24,
lipc_handle = nil,
}
function KindlePowerD:init()
local haslipc, lipc = pcall(require, "liblipclua")
if haslipc and lipc then
self.lipc_handle = lipc.init("com.github.koreader.kindlepowerd")
end
-- On devices where lipc step 0 is *not* off, we add a synthetic fl level where 0 *is* off,
-- which allows us to keep being able to use said step 0 as the first "on" step.
if not self.device:canTurnFrontlightOff() then
self.fl_max = self.fl_max + 1
end
self:initWakeupMgr()
end
-- If we start with the light off (fl_intensity is fl_min), ensure a toggle will set it to the lowest "on" step,
-- and that we update fl_intensity (by using setIntensity and not setIntensityHW).
function KindlePowerD:turnOnFrontlightHW(done_callback)
self:setIntensity(self.fl_intensity == self.fl_min and self.fl_min + 1 or self.fl_intensity)
return false
end
-- Which means we need to get rid of the insane fl_intensity == fl_min shortcut in turnOnFrontlight, too...
-- That dates back to #2941, and I have no idea what it's supposed to help with.
function KindlePowerD:turnOnFrontlight(done_callback)
if not self.device:hasFrontlight() then return end
if self:isFrontlightOn() then return false end
local cb_handled = self:turnOnFrontlightHW(done_callback)
self.is_fl_on = true
self:stateChanged()
if not cb_handled and done_callback then
done_callback()
end
return true
end
function KindlePowerD:frontlightIntensityHW()
if not self.device:hasFrontlight() then return 0 end
-- Kindle stock software does not use intensity file directly, so go through lipc to keep us in sync.
if self.lipc_handle ~= nil then
-- Handle the step 0 switcheroo on ! canTurnFrontlightOff devices...
if self.device:canTurnFrontlightOff() then
return self.lipc_handle:get_int_property("com.lab126.powerd", "flIntensity")
else
local lipc_fl_intensity = self.lipc_handle:get_int_property("com.lab126.powerd", "flIntensity")
-- NOTE: If lipc returns 0, compare against what the kernel says,
-- to avoid breaking on/off detection on devices where lipc 0 doesn't actually turn it off (<= PW3),
-- c.f., #5986
if lipc_fl_intensity == self.fl_min then
local sysfs_fl_intensity = self:_readFLIntensity()
if sysfs_fl_intensity ~= self.fl_min then
-- Return something potentially slightly off (as we can't be sure of the sysfs -> lipc mapping),
-- but, more importantly, something that's not fl_min (0), so we properly detect the light as on,
-- and update fl_intensity accordingly.
-- That's only tripped if it was set to fl_min from the stock UI,
-- as we ourselves *do* really turn it off when we do that.
return self.fl_min + 1
else
return self.fl_min
end
else
-- We've added a synthetic step...
return lipc_fl_intensity + 1
end
end
else
-- NOTE: This fallback is of dubious use, as it will NOT match our expected [fl_min..fl_max] range,
-- each model has a specific curve.
return self:_readFLIntensity()
end
end
-- Make sure isFrontlightOn reflects the actual HW state,
-- as self.fl_intensity is kept as-is when toggling the light off,
-- in order to be able to toggle it back on at the right intensity.
function KindlePowerD:isFrontlightOnHW()
local hw_intensity = self:frontlightIntensityHW()
return hw_intensity > self.fl_min
end
function KindlePowerD:setIntensityHW(intensity)
-- Handle the synthetic step switcheroo on ! canTurnFrontlightOff devices...
local turn_it_off = false
if not self.device:canTurnFrontlightOff() then
if intensity > 0 then
intensity = intensity - 1
else
-- And if we *really* requested 0, turn it off manually.
turn_it_off = true
end
end
-- NOTE: This means we *require* a working lipc handle to set the FL:
-- it knows what the UI values should map to for the specific hardware much better than us.
if self.lipc_handle ~= nil then
-- NOTE: We want to bypass setIntensity's shenanigans and simply restore the light as-is
self.lipc_handle:set_int_property("com.lab126.powerd", "flIntensity", intensity)
end
if turn_it_off then
-- NOTE: when intensity is 0, we want to *really* kill the light, so do it manually
-- (asking lipc to set it to 0 would in fact set it to > 0 on ! canTurnFrontlightOff Kindles).
-- We do *both* to make the fl restore on resume less jarring on devices where lipc 0 != off.
ffiUtil.writeToSysfs(intensity, self.fl_intensity_file)
-- And in case there are two LED groups...
-- This should never happen as all warmth devices so far canTurnFrontlightOff
if self.warmth_intensity_file then
ffiUtil.writeToSysfs(intensity, self.warmth_intensity_file)
end
end
-- The state might have changed, make sure we don't break isFrontlightOn
self:_decideFrontlightState()
end
function KindlePowerD:frontlightWarmthHW()
if self.lipc_handle ~= nil then
local nat_warmth = self.lipc_handle:get_int_property("com.lab126.powerd", "currentAmberLevel")
if nat_warmth then
-- [0...24] -> [0...100]
return self:fromNativeWarmth(nat_warmth)
else
return 0
end
end
end
function KindlePowerD:setWarmthHW(warmth)
if self.lipc_handle ~= nil then
self.lipc_handle:set_int_property("com.lab126.powerd", "currentAmberLevel", warmth)
end
end
function KindlePowerD:getCapacityHW()
if self.lipc_handle ~= nil then
return self.lipc_handle:get_int_property("com.lab126.powerd", "battLevel")
elseif self.batt_capacity_file then
return self:read_int_file(self.batt_capacity_file)
else
local std_out = io.popen("gasgauge-info -c 2>/dev/null", "r")
if std_out then
local result = std_out:read("*number")
std_out:close()
return result or 0
else
return 0
end
end
end
function KindlePowerD:isChargingHW()
local is_charging
if self.lipc_handle ~= nil then
is_charging = self.lipc_handle:get_int_property("com.lab126.powerd", "isCharging")
else
is_charging = self:read_int_file(self.is_charging_file)
end
return is_charging == 1
end
function KindlePowerD:isChargedHW()
-- Older kernels don't necessarily have this...
if self.batt_status_file then
return self:read_str_file(self.batt_status_file) == "Full"
end
return false
end
function KindlePowerD:hasHallSensor()
return self.hall_file ~= nil
end
function KindlePowerD:isHallSensorEnabled()
local int = self:read_int_file(self.hall_file)
return int == 1
end
function KindlePowerD:onToggleHallSensor()
local stat = self:isHallSensorEnabled()
ffiUtil.writeToSysfs(stat and 0 or 1, self.hall_file)
end
function KindlePowerD:_readFLIntensity()
return self:read_int_file(self.fl_intensity_file)
end
function KindlePowerD:toggleSuspend()
if self.lipc_handle then
self.lipc_handle:set_int_property("com.lab126.powerd", "powerButton", 1)
else
os.execute("powerd_test -p")
end
end
-- Kindle only allows setting the RTC via lipc during the ReadyToSuspend state
function KindlePowerD:setRtcWakeup(seconds_from_now)
if self.lipc_handle then
self.lipc_handle:set_int_property("com.lab126.powerd", "rtcWakeup", seconds_from_now)
end
end
-- Check the powerd state: are we still in screensaver mode.
function KindlePowerD:getPowerdState()
if self.lipc_handle then
return self.lipc_handle:get_string_property("com.lab126.powerd", "state")
end
end
function KindlePowerD:checkUnexpectedWakeup()
local state = self:getPowerdState()
logger.dbg("Powerd resume state:", state)
-- If we moved on to the active state,
-- then we were woken by user input not our alarm.
if state ~= "screenSaver" and state ~= "suspended" then return end
if self.device.wakeup_mgr:isWakeupAlarmScheduled() and self.device.wakeup_mgr:wakeupAction(90) then
logger.info("Kindle scheduled wakeup")
else
logger.warn("Kindle unscheduled wakeup")
end
end
-- Dummy fuctions. They will be defined in initWakeupMgr
function KindlePowerD:wakeupFromSuspend() end
function KindlePowerD:readyToSuspend() end
-- Support WakeupMgr on Lipc & supportsScreensaver devices.
function KindlePowerD:initWakeupMgr()
if not self.device:supportsScreensaver() then return end
if self.lipc_handle == nil then return end
function KindlePowerD:wakeupFromSuspend(ts)
-- Give the device a few seconds to settle.
-- This filters out user input resumes -> device will resume to active
-- Also the Kindle stays in Ready to suspend for 10 seconds
-- so the alarm may fire 10 seconds early
UIManager:scheduleIn(15, self.checkUnexpectedWakeup, self)
end
function KindlePowerD:readyToSuspend(delay)
if self.device.wakeup_mgr:isWakeupAlarmScheduled() then
local now = os.time()
local alarm = self.device.wakeup_mgr:getWakeupAlarmEpoch()
if alarm > now then
-- Powerd / Lipc need seconds_from_now not epoch
self:setRtcWakeup(alarm - now)
else
-- wakeup time is in the past
self.device.wakeup_mgr:removeTasks(alarm)
end
end
end
self.device.wakeup_mgr = WakeupMgr:new{rtc = require("device/kindle/mockrtc")}
end
-- Ask powerd to reset the t1 timeout, so that AutoSuspend can do its thing properly
function KindlePowerD:resetT1Timeout()
-- NOTE: powerd will only send a t1TimerReset event every $(kdb get system/daemon/powerd/send_t1_reset_interval) (15s),
-- which is just fine, as we should only request it at most every 5 minutes ;).
-- NOTE: This will fail if the device is already showing the screensaver.
if self.lipc_handle then
-- AFAIK, the value is irrelevant
self.lipc_handle:set_int_property("com.lab126.powerd", "touchScreenSaverTimeout", 1)
else
os.execute("lipc-set-prop -i com.lab126.powerd touchScreenSaverTimeout 1")
end
end
function KindlePowerD:beforeSuspend()
-- Inhibit user input and emit the Suspend event.
self.device:_beforeSuspend()
end
function KindlePowerD:afterResume()
self:invalidateCapacityCache()
-- Restore user input and emit the Resume event.
self.device:_afterResume()
if not self.device:hasFrontlight() then
return
end
if self:isFrontlightOn() then
-- The Kindle framework should turn the front light back on automatically.
-- The following statement ensures consistency of intensity, but should basically always be redundant,
-- since we set intensity via lipc and not sysfs ;).
-- NOTE: This is race-y, and we want to *lose* the race, hence the use of the scheduler (c.f., #4392)
UIManager:tickAfterNext(function() self:turnOnFrontlightHW() end)
else
-- But in the off case, we *do* use sysfs, so this one actually matters.
UIManager:tickAfterNext(function() self:turnOffFrontlightHW() end)
end
end
function KindlePowerD:UIManagerReadyHW(uimgr)
UIManager = uimgr
end
--- @fixme: This won't ever fire on its own, as KindlePowerD is already a metatable on a plain table.
function KindlePowerD:__gc()
if self.lipc_handle then
self.lipc_handle:close()
self.lipc_handle = nil
end
end
return KindlePowerD