Привет, ребята.
У меня есть ноутбук Lenovo ideapad G580 (20150). И у этого ноутбука под венду есть утилита под названием «lenovo energy management». Среди возможностей этой утилиты есть и функция управления зарядом батареи - полный заряд и режим защиты аккумулятора. В Linux этой фичей пользоваться нельзя, нет средств для этого (tp_smapi и tpacpi-bat только для настоящих ThinkPad'ов). Меня этот факт очень огорчал и я провел изыскания в результате которых нашел способ переключения режимов батареи.
Вообще управляется эта фигня через Super_I/O, но не суть важно.
Для начала нам нужен оффтопик с установленным «lenovo energy management» и тулзой RWEverything.
Запускаем RWEverything и тыкаем иконку EC скрин. Переключаем в «lenovo energy management» режимы батареи и смотрим какой байт меняется в окошке «Embedded Controller». В моем случае это байт по адресу «0A», 21 - защита батареи, 41 - полный заряд. А EC_SC/EC_DATA это адреса регистров, которые нам потом понадобятся. Все ясно, перезагружаемся в линукс.
Сначала я хотел использовать superiotool, но фиг там:
# superiotool
superiotool r6637
No Super I/O found
Однако удача была со мной, и я нашел fanctrl.c, который и послужил основой для моей поделки.
Собственно моя поделка:
#include <stdint.h>
#include <sys/io.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* Поменяйте на свои значения */
#define EC_SC 0x66
#define EC_DATA 0x62
#define IBF 1
#define OBF 0
#define EC_SC_READ_CMD 0x80
#define EC_SC_WRITE_CMD 0x81
#define EC_SC_SCI_CMD 0x84
#define BATT_PORT 0x0a
#define BATT_LIMIT 0x21
#define BATT_FULL 0x41
static void init()
{
if (ioperm(EC_DATA, 1, 1) != 0)
{
perror("ioperm(EC_DATA, 1, 1)");
exit(1);
}
if (ioperm(EC_SC, 1, 1) != 0)
{
perror("ioperm(EC_SC, 1, 1)");
exit(1);
}
}
static void wait_ec(const uint32_t port, const uint32_t flag, const char value)
{
uint8_t data;
int i;
i = 0;
data = inb(port);
while ( (((data >> flag) & 0x1) != value) && (i++ < 100) )
{
usleep(1000);
data = inb(port);
}
if (i >= 100)
{
fprintf(stderr, "wait_ec error on port 0x%x, data=0x%x, flag=0x%x, value=0x%x\n", port, data, flag, value);
exit(1);
}
}
static uint8_t read_ec(const uint32_t port)
{
uint8_t value;
wait_ec(EC_SC, IBF, 0);
outb(EC_SC_READ_CMD, EC_SC);
wait_ec(EC_SC, IBF, 0);
outb(port, EC_DATA);
//wait_ec(EC_SC, EC_SC_IBF_FREE);
wait_ec(EC_SC, OBF, 1);
value = inb(EC_DATA);
return value;
}
static void write_ec(const uint32_t port, const uint8_t value)
{
wait_ec(EC_SC, IBF, 0);
outb(EC_SC_WRITE_CMD, EC_SC);
wait_ec(EC_SC, IBF, 0);
outb(port, EC_DATA);
wait_ec(EC_SC, IBF, 0);
outb(value, EC_DATA);
wait_ec(EC_SC, IBF, 0);
}
static void dump_all_regs(void)
{
uint8_t val;
int i;
printf("EC reg dump:");
for (i = 0x00; i <= 0xff; i++)
{
if ((i % 16) == 0)
{
printf("\n 0x%02x: ", i);
}
val = read_ec(i);
printf("%02x ", val);
}
printf("\n");
}
static void set_value(const uint8_t value)
{
uint8_t rval;
rval = read_ec(BATT_PORT);
printf("old value %02x\n", rval);
write_ec(BATT_PORT, value);
rval = read_ec(BATT_PORT);
printf("new value %02x\n", rval);
}
int main(int argc, char *argv[])
{
init();
if (argc < 2)
{
dump_all_regs();
}
else
{
if (argv[1][0] == 'f')
{
printf("set full charge\n");
set_value(BATT_FULL);
}
else if (argv[1][0] == 'l')
{
printf("set limited charge\n");
set_value(BATT_LIMIT);
}
else
{
printf("unknown option\n");
}
}
return 0;
}
А это её работа:
# acpi
Battery 0: Unknown, 60%
# ./a.out f
set full charge
old value 21
new value 41
# acpi
Battery 0: Charging, 61%, 00:01:23 until charged
# ./a.out l
set limited charge
old value 41
new value 21
# acpi
Battery 0: Discharging, 61%, 01:52:05 remaining
# acpi
Battery 0: Discharging, 60%, 01:49:36 remaining
# acpi
Battery 0: Discharging, 60%, 01:47:34 remaining
# acpi
Battery 0: Unknown, 60%