掌機 - Miyoo Mini Flip - 移植Kernel input



一台掌機要可以運作,最基本的Kernel Module需要有顯示、聲音、輸入,目前Miyoo Mini Flip的顯示、聲音是沒有開源且沒有相關CPU Datasheet可以參考,不過司徒這次想幫Miyoo Mini Flip製作一個鍵盤+滑鼠的複合式裝置,這樣玩模擬器就可以切換成鍵盤模式或者滑鼠模式


司徒已經先將按鍵腳位測試出來

UP     = PA.1
DOWN   = PC.5
LEFT   = PC.6
RIGHT  = PA.5
A      = PA.7
B      = PA.6
X      = PA.9
Y      = PA.8
SELECT = PA.11
START  = PA.10
L1     = PA.14
L2     = PA.13
R1     = PB.15
R2     = PC.26
VOLM   = PB.10
VOLP   = PB.11
MENU   = PA.12
POWER  = PC.7

drivers/input/keyboard/mmf-keypad.c

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option)any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include <linux/fs.h>
#include <linux/kobject.h>
#include <linux/cdev.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/timer.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/backlight.h>

#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include <sound/initval.h>
#include <sound/dmaengine_pcm.h>
#include <linux/gpio.h>

#include <asm/io.h>
#include <asm/irq.h>
#include <asm/gpio.h>

#define DEBUG_LOG   0
#define MOUSE_MODE  0
#define KEYPAD_MODE 1

static int myperiod = 30;
static int mouse_left = 0;
static int mouse_right = 0;
static int mode = KEYPAD_MODE;
static struct input_dev *mydev;
static struct timer_list mytimer;

static uint32_t R_UP     = 0x00001;
static uint32_t R_DOWN   = 0x00002;
static uint32_t R_LEFT   = 0x00004;
static uint32_t R_RIGHT  = 0x00008;
static uint32_t R_A      = 0x00010;
static uint32_t R_B      = 0x00020;
static uint32_t R_X      = 0x00040;
static uint32_t R_Y      = 0x00080;
static uint32_t R_SELECT = 0x00100;
static uint32_t R_START  = 0x00200;
static uint32_t R_L1     = 0x00400;
static uint32_t R_L2     = 0x00800;
static uint32_t R_R1     = 0x01000;
static uint32_t R_R2     = 0x02000;
static uint32_t R_MENU   = 0x04000;
static uint32_t R_VOLM   = 0x08000;
static uint32_t R_VOLP   = 0x10000;
static uint32_t R_POWER  = 0x20000;

/*
UP     = PA.1
DOWN   = PC.5
LEFT   = PC.6
RIGHT  = PA.5
A      = PA.7
B      = PA.6
X      = PA.9
Y      = PA.8
SELECT = PA.11
START  = PA.10
L1     = PA.14
L2     = PA.13
R1     = PB.15
R2     = PC.26
VOLM   = PB.10
VOLP   = PB.11
MENU   = PA.12
POWER  = PC.7
*/

static uint32_t I_UP     = 1;
static uint32_t I_DOWN   = 69;
static uint32_t I_LEFT   = 70;
static uint32_t I_RIGHT  = 5;
static uint32_t I_A      = 7;
static uint32_t I_B      = 6;
static uint32_t I_X      = 9;
static uint32_t I_Y      = 8;
static uint32_t I_SELECT = 11;
static uint32_t I_START  = 10;
static uint32_t I_L1     = 14;
static uint32_t I_L2     = 13;
static uint32_t I_R1     = 47;
static uint32_t I_R2     = 90;
static uint32_t I_VOLM   = 42;
static uint32_t I_VOLP   = 43;
static uint32_t I_MENU   = 12;
static uint32_t I_POWER  = 71;

static int do_input_request(uint32_t pin, const char *name)
{
    if (gpio_request(pin, name) < 0) {
        printk("failed to request gpio: %s\n", name);
        return -1;
    }
    gpio_direction_input(pin);

    return 0;
}

static void print_key(uint32_t val, uint8_t is_pressed)
{
#if DEBUG_LOG
    uint32_t i = 0;
    uint32_t map_val[] = {
        R_UP, 
        R_DOWN, 
        R_LEFT, 
        R_RIGHT, 
        R_A, 
        R_B, 
        R_X, 
        R_Y, 
        R_L1, 
        R_L2, 
        R_R1, 
        R_R2, 
        R_SELECT, 
        R_START, 
        R_MENU, 
        R_VOLM, 
        R_VOLP, 
        R_POWER, 
        -1
    };

    char *map_key[] = {
        "UP", 
        "DOWN", 
        "LEFT", 
        "RIGHT", 
        "A", 
        "B", 
        "X", 
        "Y", 
        "L1", 
        "L2", 
        "R1", 
        "R2", 
        "SELECT", 
        "START", 
        "MENU", 
        "VOL-", 
        "VOL+", 
        "POWER", 
        ""
    };

    for (i = 0; map_val[i] != -1; i++) {
        if (map_val[i] == val) {
            printk("%s: %s\n", map_key[i], is_pressed ? "Pressed" : "Released");
            break;
        }
    }
#endif
}

static void report_key(uint32_t btn, uint32_t mask, uint8_t key)
{
    static uint32_t btn_pressed = 0;
    static uint32_t btn_released = 0xffffffff;

    if (btn & mask) {
        btn_released &= ~mask;
        if ((btn_pressed & mask) == 0) {
            btn_pressed |= mask;
            input_report_key(mydev, key, 1);
            print_key(mask, 1);
        }
    }
    else {
        btn_pressed &= ~mask;
        if((btn_released & mask) == 0) {
            btn_released |= mask;
            input_report_key(mydev, key, 0);
            print_key(mask, 0);
        }
    }
}

static void input_handler(unsigned long unused)
{
    uint32_t val = 0;
    static uint32_t pre = 0;

    if (gpio_get_value(I_UP) == 0)       val |= R_UP;
    if (gpio_get_value(I_DOWN) == 0)     val |= R_DOWN;
    if (gpio_get_value(I_LEFT) == 0)     val |= R_LEFT;
    if (gpio_get_value(I_RIGHT) == 0)    val |= R_RIGHT;
    if (gpio_get_value(I_A) == 0)        val |= R_A;
    if (gpio_get_value(I_B) == 0)        val |= R_B;
    if (gpio_get_value(I_X) == 0)        val |= R_X;
    if (gpio_get_value(I_Y) == 0)        val |= R_Y;
    if (gpio_get_value(I_L1) == 0)       val |= R_L1;
    if (gpio_get_value(I_L2) == 0)       val |= R_L2;
    if (gpio_get_value(I_R1) == 0)       val |= R_R1;
    if (gpio_get_value(I_R2) == 0)       val |= R_R2;
    if (gpio_get_value(I_SELECT) == 0)   val |= R_SELECT;
    if (gpio_get_value(I_START) == 0)    val |= R_START;
    if (gpio_get_value(I_MENU) == 0)     val |= R_MENU;
    if (gpio_get_value(I_VOLM) == 0)     val |= R_VOLM;
    if (gpio_get_value(I_VOLP) == 0)     val |= R_VOLP;
    if (gpio_get_value(I_POWER) == 0)    val |= R_POWER;

    if (pre != val) {
        pre = val;
        if (mode == KEYPAD_MODE) {
            report_key(pre, R_UP,     KEY_UP);
            report_key(pre, R_DOWN,   KEY_DOWN);
            report_key(pre, R_LEFT,   KEY_LEFT);
            report_key(pre, R_RIGHT,  KEY_RIGHT);
            report_key(pre, R_A,      KEY_LEFTCTRL);
            report_key(pre, R_B,      KEY_LEFTALT);
            report_key(pre, R_X,      KEY_SPACE);
            report_key(pre, R_Y,      KEY_LEFTSHIFT);
            report_key(pre, R_L1,     KEY_TAB);
            report_key(pre, R_L2,     KEY_PAGEUP);
            report_key(pre, R_R1,     KEY_BACKSPACE);
            report_key(pre, R_R2,     KEY_PAGEDOWN);
            report_key(pre, R_SELECT, KEY_ESC);
            report_key(pre, R_START,  KEY_ENTER);
            report_key(pre, R_MENU,   KEY_HOME);
            report_key(pre, R_VOLP,   KEY_VOLUMEUP);
            report_key(pre, R_VOLM,   KEY_VOLUMEDOWN);
            report_key(pre, R_POWER,  KEY_POWER);
        }
        input_sync(mydev);
    }
    if (mode == MOUSE_MODE) {
        int need_sync = 0;

        if (val & R_UP) {
            need_sync = 1;
            input_report_rel(mydev, REL_Y, -5);
        }
        if (val & R_DOWN) {
            need_sync = 1;
            input_report_rel(mydev, REL_Y, 5);
        }
        if (val & R_LEFT) {
            need_sync = 1;
            input_report_rel(mydev, REL_X, -5);
        }
        if (val & R_RIGHT) {
            need_sync = 1;
            input_report_rel(mydev, REL_X, 5);
        }

        if ((!!(val & R_A)) != mouse_right){
            mouse_right = !!(val & R_A);
            need_sync = 1;
        }
        if ((!!(val & R_Y)) != mouse_left){
            mouse_left = !!(val & R_Y);
            need_sync = 1;
        }

        if (need_sync){
            input_report_key(mydev, BTN_RIGHT, mouse_right);
            input_report_key(mydev, BTN_LEFT, mouse_left);
            input_mt_sync(mydev);
            input_sync(mydev);
        }
    }
    mod_timer(&mytimer, jiffies + msecs_to_jiffies(myperiod));
}

static ssize_t mode_show(struct device *dev, struct device_attribute *attr, char *buf)
{
    return sprintf(buf, "%s\n", mode == KEYPAD_MODE ? "keypad" : "mouse");
}

static ssize_t mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
    int rc = -1;
    unsigned long v = 0;

    rc = kstrtoul(buf, 0, &v);
    if (rc) {
        return rc;
    }

    if (v == KEYPAD_MODE) {
        mode = KEYPAD_MODE;
    }
    else {
        mode = MOUSE_MODE;
    }
    return count;
}
static DEVICE_ATTR_RW(mode);

static int __init kbd_init(void)
{
    uint32_t ret = 0;

    do_input_request(I_UP,     "up");
    do_input_request(I_DOWN,   "down");
    do_input_request(I_LEFT,   "left");
    do_input_request(I_RIGHT,  "right");
    do_input_request(I_A,      "a");
    do_input_request(I_B,      "b");
    do_input_request(I_X,      "x");
    do_input_request(I_Y,      "y");
    do_input_request(I_SELECT, "select");
    do_input_request(I_START,  "start");
    do_input_request(I_L1,     "l1");
    do_input_request(I_L2,     "l2");
    do_input_request(I_R1,     "r1");
    do_input_request(I_R2,     "r2");
    do_input_request(I_VOLM,   "vol-");
    do_input_request(I_VOLP,   "vol+");
    do_input_request(I_MENU,   "menu");
    do_input_request(I_POWER,  "power");

    mydev = input_allocate_device();
    set_bit(EV_KEY,         mydev->evbit);
    set_bit(KEY_UP,         mydev->keybit);
    set_bit(KEY_DOWN,       mydev->keybit);
    set_bit(KEY_LEFT,       mydev->keybit);
    set_bit(KEY_RIGHT,      mydev->keybit);
    set_bit(KEY_LEFTCTRL,   mydev->keybit);
    set_bit(KEY_LEFTALT,    mydev->keybit);
    set_bit(KEY_SPACE,      mydev->keybit);
    set_bit(KEY_LEFTSHIFT,  mydev->keybit);
    set_bit(KEY_ENTER,      mydev->keybit);
    set_bit(KEY_ESC,        mydev->keybit);
    set_bit(KEY_TAB,        mydev->keybit);
    set_bit(KEY_PAGEDOWN,   mydev->keybit);
    set_bit(KEY_BACKSPACE,  mydev->keybit);
    set_bit(KEY_PAGEUP,     mydev->keybit);
    set_bit(KEY_HOME,       mydev->keybit);
    set_bit(KEY_VOLUMEUP,   mydev->keybit);
    set_bit(KEY_VOLUMEDOWN, mydev->keybit);
    set_bit(KEY_POWER,      mydev->keybit);
    set_bit(BTN_LEFT,       mydev->keybit);
    set_bit(BTN_MIDDLE,     mydev->keybit);
    set_bit(BTN_RIGHT,      mydev->keybit);

    set_bit(EV_REL,         mydev->evbit);
    set_bit(REL_X,          mydev->relbit);
    set_bit(REL_Y,          mydev->relbit);
    set_bit(REL_WHEEL,      mydev->relbit);

    mydev->name = "mmf-keypad";
    mydev->id.bustype = BUS_HOST;
    ret = input_register_device(mydev);
    device_create_file(&mydev->dev, &dev_attr_mode);

    setup_timer(&mytimer, input_handler, 0);
    mod_timer(&mytimer, jiffies + msecs_to_jiffies(myperiod));

    return 0;
}

static void __exit kbd_exit(void)
{
    device_remove_file(&mydev->dev, &dev_attr_mode);
    input_unregister_device(mydev);
    del_timer(&mytimer);
}

module_init(kbd_init);
module_exit(kbd_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Steward Fu <steward.fu@gmail.com>");
MODULE_DESCRIPTION("keypad driver for Miyoo Mini Flip handheld");

drivers/input/keyboard/Kconfig

config KEYBOARD_MMF
    tristate "keypad driver for Miyoo Mini Flip"
    depends on OF
    help
      Say Y here if you want to use keypad driver for Miyoo Mini Flip handheld.

      To compile this driver as a module, choose M here: the 
      module will be called mmf-keypad.

drivers/input/keyboard/Makefile

obj-$(CONFIG_KEYBOARD_MMF) += mmf-keypad.o

Device Drivers => Input device support => Keyboards


架構是長這樣


接著我們使用一個SDL測試程式用來測試鍵盤和滑鼠功能

#include <stdio.h>
#include <stdlib.h>
#include <SDL.h>
#include <SDL_gfxPrimitives.h>
 
int main(int argc, char **args)
{
    int mode = 0;
    char buf[255] = {0};
    SDL_Surface *screen = NULL;
 
    SDL_Init(SDL_INIT_VIDEO);
    screen = SDL_SetVideoMode(320, 240, 16, SDL_HWSURFACE);
    SDL_FillRect(screen, &screen->clip_rect, SDL_MapRGB(screen->format, 0x00, 0x00, 0x00));
    SDL_Flip(screen);
 
    SDL_Event event;
    while (1) {
        if (SDL_PollEvent(&event)) {
            if ((event.type == SDL_KEYDOWN) && (event.key.keysym.sym > 0)){
                sprintf(buf, "key: 0x%x", event.key.keysym.sym);
                printf("%s\n", buf);
 
                if (event.key.keysym.sym == 0x116) {
                    char t[128] = { 0 };

                    sprintf(t, "echo %d > /sys/devices/virtual/input/input0/mode", !!mode);
                    system(t);
                    printf("%s\n", t);
                    mode ^= 1;
                }
            }
            else if (event.type == SDL_MOUSEMOTION) {
                int x = 0, y = 0;
 
                SDL_GetMouseState(&x, &y);
                sprintf(buf, "mouse pos: %d %d", x, y);
                printf("%s\n", buf);
            }
            else if (event.type == SDL_MOUSEBUTTONUP) {
                sprintf(buf, "mouse button up");
                printf("%s\n", buf);
            }
            else if (event.type == SDL_MOUSEBUTTONDOWN) {
                sprintf(buf, "mouse button down");
                printf("%s\n", buf);
            }

            SDL_FillRect(screen, &screen->clip_rect, SDL_MapRGB(screen->format, 0x00, 0x00, 0x00));
            stringRGBA(screen, 100, 100, buf, 0xff, 0xff, 0xff, 0xff);
            SDL_Flip(screen);
        }
        SDL_Delay(100);
    }
    SDL_Quit();
    return 0;
}

MENU用來切換按鍵和滑鼠功能


從測試可以發現,如果沒有修改SDL元件庫,畫面是上下相反的