Skip to content

Commit

Permalink
drm/loongson: Add GPIO and I2C driver for loongson drm.
Browse files Browse the repository at this point in the history
Implement use GPIO and I2C driver to detect connector
and fetch EDID via DDC.

v5:
- Use braidge->ddc to get EDID and detect connector.

v4:
- Delete the gpio_chip subsystem call.
- Delete some redundant prints.

v3:
- Change some driver log to the drm_ version.

v2:
- Optimize the error handling process.
- Delete loongson_i2c_bus_match and loongson_i2c_add function.
- Optimize part of the code flow.

Signed-off-by: Yi Li <liyi@loongson.cn>
Signed-off-by: Chenyang Li <lichenyang@loongson.cn>
  • Loading branch information
loongsonLCY authored and intel-lab-lkp committed Apr 22, 2022
1 parent e9a9964 commit 4a5b6ac
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 10 deletions.
1 change: 1 addition & 0 deletions drivers/gpu/drm/loongson/Makefile
Expand Up @@ -9,5 +9,6 @@ loongson-y := loongson_connector.o \
loongson_device.o \
loongson_drv.o \
loongson_encoder.o \
loongson_i2c.o \
loongson_plane.o
obj-$(CONFIG_DRM_LOONGSON) += loongson.o
13 changes: 11 additions & 2 deletions drivers/gpu/drm/loongson/loongson_drv.c
Expand Up @@ -22,9 +22,10 @@

/* Interface history:
* 0.1 - original.
* 0.2 - add i2c and connector detect.
*/
#define DRIVER_MAJOR 0
#define DRIVER_MINOR 1
#define DRIVER_MINOR 2

static const struct drm_mode_config_funcs loongson_mode_funcs = {
.fb_create = drm_gem_fb_create,
Expand Down Expand Up @@ -88,6 +89,14 @@ static int loongson_device_init(struct drm_device *dev)

ldev->num_crtc = 2;

ret = loongson_dc_gpio_init(ldev);
if (ret)
return ret;

ret = loongson_i2c_init(ldev);
if (ret)
return ret;

drm_info(dev, "DC mmio base 0x%llx size 0x%llx io 0x%llx\n",
mmio_base, mmio_size, *(u64 *)ldev->io);
drm_info(dev, "GPU vram start = 0x%x size = 0x%x\n",
Expand All @@ -96,7 +105,7 @@ static int loongson_device_init(struct drm_device *dev)
return 0;
}

int loongson_modeset_init(struct loongson_device *ldev)
static int loongson_modeset_init(struct loongson_device *ldev)
{
int i;
int ret;
Expand Down
7 changes: 7 additions & 0 deletions drivers/gpu/drm/loongson/loongson_drv.h
Expand Up @@ -10,6 +10,8 @@
#include <drm/drm_gem_vram_helper.h>
#include <drm/drm_bridge.h>

#include "loongson_i2c.h"

/* General customization:
*/
#define DRIVER_AUTHOR "Loongson graphics driver team"
Expand Down Expand Up @@ -102,6 +104,8 @@ struct loongson_device {
u32 num_crtc;
struct loongson_mode_info mode_info[2];
struct pci_dev *gpu_pdev; /* LS7A gpu device info */

struct loongson_i2c i2c_bus[DC_MAX_I2C_BUS];
};

static inline struct loongson_device *to_loongson_device(struct drm_device *dev)
Expand All @@ -121,6 +125,9 @@ int loongson_encoder_init(struct loongson_device *ldev, int index);
/* plane */
struct loongson_plane *loongson_plane_init(struct drm_device *dev, int index);

/* i2c */
int loongson_dc_gpio_init(struct loongson_device *ldev);

/* device */
u32 loongson_gpu_offset(struct drm_plane_state *state,
struct loongson_device *dev);
Expand Down
31 changes: 23 additions & 8 deletions drivers/gpu/drm/loongson/loongson_encoder.c
Expand Up @@ -7,19 +7,31 @@

#include "loongson_drv.h"

static int loongson_bridge_get_modes(struct drm_bridge *bridge,
struct drm_connector *connector)
enum drm_connector_status loongson_bridge_detect(struct drm_bridge *bridge)
{
int count;
unsigned char start = 0x0;
struct i2c_msg msgs = {
.addr = DDC_ADDR,
.flags = 0,
.len = 1,
.buf = &start,
};

count = drm_add_modes_noedid(connector, 1920, 1080);
drm_set_preferred_mode(connector, 1024, 768);
if (i2c_transfer(bridge->ddc, &msgs, 1) != 1)
return connector_status_disconnected;
else
return connector_status_connected;
}

return count;
static struct edid *loongson_bridge_get_edid(struct drm_bridge *bridge,
struct drm_connector *connector)
{
return drm_get_edid(connector, bridge->ddc);
}

static const struct drm_bridge_funcs loongson_encoder_bridge_funcs = {
.get_modes = loongson_bridge_get_modes,
.detect = loongson_bridge_detect,
.get_edid = loongson_bridge_get_edid,
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_reset = drm_atomic_helper_bridge_reset,
Expand All @@ -40,7 +52,10 @@ int loongson_encoder_init(struct loongson_device *ldev, int index)
ldev->mode_info[index].encoder = lencoder;

lencoder->bridge.funcs = &loongson_encoder_bridge_funcs;
lencoder->bridge.ops = DRM_BRIDGE_OP_MODES;
lencoder->bridge.ddc = ldev->i2c_bus[index].adapter;
lencoder->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID
| DRM_BRIDGE_OP_MODES;

if (index == 0)
lencoder->bridge.type = DRM_MODE_CONNECTOR_VGA;
else if (index == 1)
Expand Down
191 changes: 191 additions & 0 deletions drivers/gpu/drm/loongson/loongson_i2c.c
@@ -0,0 +1,191 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2020-2022 Loongson Technology Corporation Limited
*/

#include "loongson_drv.h"
#include "loongson_i2c.h"

static inline void dc_gpio_set_dir(struct loongson_device *ldev,
unsigned int pin, int input)
{
u32 temp;

temp = ls7a_mm_rreg(ldev, LS7A_DC_GPIO_CFG_OFFSET);
if (input)
temp |= 1UL << pin;
else
temp &= ~(1UL << pin);

ls7a_mm_wreg(ldev, LS7A_DC_GPIO_CFG_OFFSET, temp);
}

static void dc_gpio_set_val(struct loongson_device *ldev, unsigned int pin,
int high)
{
u32 temp;

temp = ls7a_mm_rreg(ldev, LS7A_DC_GPIO_OUT_OFFSET);
if (high)
temp |= 1UL << pin;
else
temp &= ~(1UL << pin);

ls7a_mm_wreg(ldev, LS7A_DC_GPIO_OUT_OFFSET, temp);
}

static void loongson_i2c_set_data(void *i2c, int value)
{
struct loongson_i2c *li2c = i2c;
struct loongson_device *ldev = li2c->ldev;
unsigned int pin = li2c->data;

if (value)
dc_gpio_set_dir(ldev, pin, 1);
else {
dc_gpio_set_val(ldev, pin, 0);
dc_gpio_set_dir(ldev, pin, 0);
}
}

static void loongson_i2c_set_clock(void *i2c, int value)
{
struct loongson_i2c *li2c = i2c;
struct loongson_device *ldev = li2c->ldev;
unsigned int pin = li2c->clock;

if (value)
dc_gpio_set_dir(ldev, pin, 1);
else {
dc_gpio_set_val(ldev, pin, 0);
dc_gpio_set_dir(ldev, pin, 0);
}
}

static int loongson_i2c_get_data(void *i2c)
{
int val;
struct loongson_i2c *li2c = i2c;
struct loongson_device *ldev = li2c->ldev;
unsigned int pin = li2c->data;

val = ls7a_mm_rreg(ldev, LS7A_DC_GPIO_IN_OFFSET);

return (val >> pin) & 1;
}

static int loongson_i2c_get_clock(void *i2c)
{
int val;
struct loongson_i2c *li2c = i2c;
struct loongson_device *ldev = li2c->ldev;
unsigned int pin = li2c->clock;

val = ls7a_mm_rreg(ldev, LS7A_DC_GPIO_IN_OFFSET);

return (val >> pin) & 1;
}

static int loongson_i2c_create(struct loongson_device *ldev,
struct loongson_i2c *li2c, const char *name)
{
int ret;
unsigned int i2c_num;
struct drm_device *dev = &ldev->dev;
struct i2c_client *i2c_cli;
struct i2c_adapter *i2c_adapter;
struct i2c_algo_bit_data *i2c_algo_data;
const struct i2c_board_info i2c_info = {
.type = "ddc-dev",
.addr = DDC_ADDR,
.flags = I2C_CLASS_DDC,
};

i2c_num = li2c->i2c_id;
i2c_adapter = devm_kzalloc(dev->dev, sizeof(*i2c_adapter), GFP_KERNEL);
if (!i2c_adapter)
return -ENOMEM;

i2c_algo_data = devm_kzalloc(dev->dev, sizeof(*i2c_algo_data), GFP_KERNEL);
if (!i2c_algo_data) {
ret = -ENOMEM;
goto free_adapter;
}

i2c_adapter->owner = THIS_MODULE;
i2c_adapter->class = I2C_CLASS_DDC;
i2c_adapter->algo_data = i2c_algo_data;
i2c_adapter->dev.parent = dev->dev;
i2c_adapter->nr = -1;
snprintf(i2c_adapter->name, sizeof(i2c_adapter->name), "%s%d",
name, i2c_num);

li2c->data = i2c_num * 2;
li2c->clock = i2c_num * 2 + 1;
DRM_INFO("Created i2c-%d, sda=%d, scl=%d\n",
i2c_num, li2c->data, li2c->clock);

i2c_algo_data->setsda = loongson_i2c_set_data;
i2c_algo_data->setscl = loongson_i2c_set_clock;
i2c_algo_data->getsda = loongson_i2c_get_data;
i2c_algo_data->getscl = loongson_i2c_get_clock;
i2c_algo_data->udelay = DC_I2C_TON;
i2c_algo_data->timeout = usecs_to_jiffies(2200);

ret = i2c_bit_add_numbered_bus(i2c_adapter);
if (ret)
goto free_algo_data;

li2c->adapter = i2c_adapter;
i2c_algo_data->data = li2c;
i2c_set_adapdata(li2c->adapter, li2c);
li2c->ldev = ldev;
DRM_INFO("Register i2c algo-bit adapter [%s]\n", i2c_adapter->name);

i2c_cli = i2c_new_client_device(i2c_adapter, &i2c_info);
if (IS_ERR(i2c_cli)) {
ret = PTR_ERR(i2c_cli);
goto remove_i2c_adapter;
}

return 0;

remove_i2c_adapter:
drm_err(dev, "Failed to create i2c client\n");
i2c_del_adapter(i2c_adapter);
free_algo_data:
drm_err(dev, "Failed to register i2c adapter %s\n", i2c_adapter->name);
kfree(i2c_algo_data);
free_adapter:
kfree(i2c_adapter);

return ret;
}

int loongson_dc_gpio_init(struct loongson_device *ldev)
{
int pin;

/* set gpio dir output 0-3 */
for (pin = 0; pin < 4; pin++) {
dc_gpio_set_val(ldev, pin, 0);
dc_gpio_set_dir(ldev, pin, 0);
}

return 0;
}

int loongson_i2c_init(struct loongson_device *ldev)
{
int ret;
int i;

for (i = 0; i < 2; i++) {
ldev->i2c_bus[1].i2c_id = i;
ret = loongson_i2c_create(ldev, &ldev->i2c_bus[i], DC_I2C_NAME);
if (ret)
return ret;
}

return 0;
}
33 changes: 33 additions & 0 deletions drivers/gpu/drm/loongson/loongson_i2c.h
@@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2020-2022 Loongson Technology Corporation Limited
*/

#ifndef __LOONGSON_I2C_H__
#define __LOONGSON_I2C_H__

#include <linux/i2c.h>
#include <linux/i2c-algo-bit.h>

#include <drm/drm_edid.h>

#define DC_I2C_TON 5
#define DC_I2C_NAME "ls_dc_i2c"
#define DC_MAX_I2C_BUS 2

#define LS7A_DC_GPIO_CFG_OFFSET (0x1660)
#define LS7A_DC_GPIO_IN_OFFSET (0x1650)
#define LS7A_DC_GPIO_OUT_OFFSET (0x1650)

struct loongson_device;
struct loongson_i2c {
struct loongson_device *ldev;
struct i2c_adapter *adapter;
u32 data;
u32 clock;
u32 i2c_id;
};

int loongson_i2c_init(struct loongson_device *ldev);

#endif /* __LOONGSON_I2C_H__ */

0 comments on commit 4a5b6ac

Please sign in to comment.