ref: 5a3aea0ca8fc8ec5529a5328e6f7879ad6d976da
parent: e7f003c9207082d683575f48c92e40a44b7d04ae
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Tue Sep 6 20:52:49 EDT 2022
imx8/pm: provide acpi(1)-compatible battery readings
--- a/sys/src/9/imx8/main.c
+++ b/sys/src/9/imx8/main.c
@@ -265,6 +265,30 @@
addphysseg(&seg);
}
+static void
+lpcspiinit(void)
+{
+ Physseg seg;
+
+ iomuxpad("pad_ecspi2_sclk", "ecspi2_sclk", "~LVTTL ~HYS ~PUE ~ODE FAST 45_OHM");
+ iomuxpad("pad_ecspi2_mosi", "ecspi2_mosi", "~LVTTL ~HYS ~PUE ~ODE FAST 45_OHM");
+ iomuxpad("pad_ecspi2_miso", "ecspi2_miso", "~LVTTL ~HYS ~PUE ~ODE FAST 45_OHM");
+ iomuxpad("pad_ecspi2_ss0", "ecspi2_ss0", "~LVTTL ~HYS ~PUE ~ODE FAST 45_OHM");
+
+ setclkgate("ecspi2.ipg_clk", 0);
+ setclkgate("ecspi2.ipg_clk_per", 0);
+ setclkrate("ecspi2.ipg_clk_per", "osc_25m_ref_clk", 25*Mhz);
+ setclkgate("ecspi2.ipg_clk_per", 1);
+ setclkgate("ecspi2.ipg_clk", 1);
+
+ memset(&seg, 0, sizeof(seg));
+ seg.attr = SG_PHYSICAL | SG_DEVICE | SG_NOEXEC;
+ seg.name = "ecspi2";
+ seg.pa = VIRTIO + 0x830000 - KZERO;
+ seg.size = BY2PG;
+ addphysseg(&seg);
+}
+
void
main(void)
{
@@ -305,6 +329,7 @@
gpioinit();
lcdinit();
tmuinit();
+ lpcspiinit();
userinit();
mpinit();
mmu0clear((uintptr*)L1);
--- a/sys/src/cmd/reform/pm.c
+++ b/sys/src/cmd/reform/pm.c
@@ -9,8 +9,17 @@
Mhz = 1000*1000,
Pwmsrcclk = 25*Mhz,
+ Scharge = 0,
+ Sovervolted,
+ Scooldown,
+ Sundervolted,
+ Smissing,
+ Sfullycharged,
+ Spowersave,
+
Light = 1,
Temp,
+ Battery,
PWMSAR = 0x0c/4,
PWMPR = 0x10/4,
@@ -39,10 +48,32 @@
TMUTTR2CR = 0xf18/4,
TMUTTR3CR = 0xf1c/4,
CR_CAL_PTR_SHIFT = 16,
-
+
+ SPIx_RXDATA = 0x00/4,
+ SPIx_TXDATA = 0x04/4,
+ SPIx_CONREG = 0x08/4,
+ CON_BURST_LENGTH = 1<<20,
+ CON_PRE_DIVIDER = 1<<12,
+ CON_POST_DIVIDER = 1<<8,
+ CON_CHAN_MASTER = 1<<4,
+ CON_XCH = 1<<2,
+ CON_EN = 1<<0,
+ SPIx_CONFIGREG = 0x0c/4,
+ CONFIG_SCLK_CTL_LOW = 0<<20,
+ CONFIG_DATA_CTL_HIGH = 0<<16,
+ CONFIG_SS_POL_LOW = 0<<12,
+ CONFIG_SS_CTL_NCSS = 1<<8,
+ CONFIG_SCLK_POL_HIGH = 0<<4,
+ CONFIG_SCLK_PHA_1 = 1<<0,
+ SPIx_INTREG = 0x10/4,
+ SPIx_STATREG = 0x18/4,
+ STAT_TC = 1<<7,
+ STAT_RR = 1<<3,
+ STAT_TE = 1<<0,
+ SPIx_PERIODREG = 0x1c/4,
};
-static u32int *pwm2, *tmu;
+static u32int *pwm2, *tmu, *spi2;
static char *uid = "pm";
static void
@@ -149,6 +180,107 @@
}
static void
+lpccall(char cmd, u8int arg, void *ret)
+{
+ u32int con;
+ int i;
+
+ con =
+ /* 8 bits burst */
+ CON_BURST_LENGTH*(8-1) |
+ /* clk=25Mhz, pre=1, post=2⁶ → 25Mhz/1/2⁶ ≲ 400kHz */
+ CON_PRE_DIVIDER*(1-1) | CON_POST_DIVIDER*6 |
+ /* master mode on channel 0 */
+ CON_CHAN_MASTER<<0 |
+ /* enable */
+ CON_EN;
+ wr(spi2, SPIx_CONREG, con);
+
+ wr(spi2, SPIx_CONFIGREG,
+ /* defaults */
+ CONFIG_SCLK_CTL_LOW |
+ CONFIG_DATA_CTL_HIGH |
+ CONFIG_SS_POL_LOW |
+ CONFIG_SCLK_POL_HIGH |
+ /* tx shift - rising edge SCLK; tx latch - falling edge */
+ CONFIG_SCLK_PHA_1 |
+ CONFIG_SS_CTL_NCSS);
+
+ wr(spi2, SPIx_TXDATA, 0xb5);
+ wr(spi2, SPIx_TXDATA, cmd);
+ wr(spi2, SPIx_TXDATA, arg);
+ wr(spi2, SPIx_CONREG, con | CON_XCH);
+
+ /*
+ * give enough time to send and for LPC to process
+ * 50ms seems safe but add more just in case
+ */
+ sleep(75);
+ /* LPC buffers 3 bytes without responding, ignore */
+ for(i = 0; i < 3; i++)
+ rd(spi2, SPIx_RXDATA);
+
+ /*
+ * at this point LPC hopefully is blocked waiting for
+ * chip select to go active
+ */
+
+ /* expecting 8 bytes, start the exchange */
+ for(i = 0; i < 8; i++)
+ wr(spi2, SPIx_TXDATA, 0);
+ wr(spi2, SPIx_CONREG, con | CON_XCH);
+
+ for(i = 0; i < 8; i++){
+ do{
+ sleep(10);
+ }while((rd(spi2, SPIx_STATREG) & STAT_RR) == 0);
+ ((u8int*)ret)[i] = rd(spi2, SPIx_RXDATA);
+ }
+
+ wr(spi2, SPIx_CONREG, con & ~CON_EN);
+}
+
+static int
+readbattery(char *buf, int sz)
+{
+ u8int st[8], ch[8];
+ char *state;
+ s16int current;
+
+ lpccall('c', 0, ch);
+ lpccall('q', 0, st);
+ current = (s16int)(st[2] | st[3]<<8);
+
+ state = "unknown";
+ if(st[5] == Scharge){
+ if(current < 0)
+ state = "charging";
+ else if(current > 0)
+ state = "discharging";
+ }else{
+ switch(st[5]){
+ case Sfullycharged: state = "full"; break;
+ case Smissing: state = "missing"; break;
+ case Sovervolted: state = "overvolted"; break;
+ case Scooldown: state = "cooldown"; break;
+ case Spowersave: state = "powersave"; break;
+ }
+ }
+
+ if(st[5] != Smissing){
+ snprint(buf, sz, "%d mA %d %d %d ? %d mV %d ? ??:??:?? %s\n",
+ st[4],
+ ch[0]|ch[1]<<8, ch[4]|ch[5]<<8, ch[4]|ch[5]<<8, ch[2]|ch[3]<<8,
+ st[0]|st[1]<<8,
+ state
+ );
+ return 0;
+ }
+
+ return -1;
+}
+
+static void
fsread(Req *r)
{
char msg[256];
@@ -163,6 +295,11 @@
snprint(msg, sizeof(msg), "%d.0\n", c[0]);
else
snprint(msg, sizeof(msg), "%r\n");
+ }else if(r->fid->file->aux == (void*)Battery){
+ if(readbattery(msg, sizeof(msg)) != 0){
+ respond(r, "missing");
+ return;
+ }
}
}
@@ -233,8 +370,11 @@
sysfatal("no tmu");
if((pwm2 = segattach(0, "pwm2", 0, 0x18)) == (void*)-1)
sysfatal("no pwm2");
+ if((spi2 = segattach(0, "ecspi2", 0, 0x20)) == (void*)-1)
+ sysfatal("no spi2");
tmuinit();
fs.tree = alloctree(uid, uid, DMDIR|0555, nil);
+ createfile(fs.tree->root, "battery", uid, 0444,(void*)Battery);
createfile(fs.tree->root, "cputemp", uid, 0444, (void*)Temp);
createfile(fs.tree->root, "light", uid, 0666, (void*)Light);
postmountsrv(&fs, srv, mtpt, MAFTER);