Re: Anysee E30C Plus (Re: DVB-C developers interested in receiving an USB adapter for hacking?)]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Antti Palosaari wrote:
morjens
Here comes the second test version. It has now remote control support. My next plan is to fix TDA10023 issue correctly. After that I will try to add Anysee DVB-T support also. All Anysee devices seems to be rather similar (demod & tuner of course differs).

TODO:
- put required TDA10023 options configurable
- add Anysee E30 DVB-T device support
- add Common Interface (CI) support
- fixes overall

Please test and report whether or not it works.

Regards,
Antti

Hi Antti + list,

First of all thanks of the work you all have been doing.

I just tried your version of the Anysee driver on my E30 DVB-T card. I have understood that it works for some people. According to my experiments it seems that my device does not have mt352 but zl10353 module. Frontend was missing during the first tests. I did some crude hacks to the anysee.c file (practically switching mt352 to zl10353) and managed to make my card work.

It's most likely not a big task to make it work on both versions but I think it's better that it's done by someone who has done changes before. I'm happy to give testing and other support if needed. There is some Finnish discussion going on on Finnish Ubuntu forum.

Link:
http://forum.ubuntu-fi.org/index.php?PHPSESSID=1dad860d0831613e4a758d321b33a126&topic=1805.40

I attach the hacked file in case someone wants to try it out.

Best Regards,
Heikki


/* DVB USB Linux driver for Anysee E30 DVB-C & DVB-T USB2.0 receiver
 *
 * Copyright (C) 2007 Antti Palosaari <crope@xxxxxx>
 *
 *	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, version 2.
 *
 * see Documentation/dvb/README.dvb-usb for more information
 *
 *
 * TODO:
 * - put required TDA10023 options configurable
 * - add Common Interface (CI) support
 * - fixes overall
 */

#include "anysee.h"
#include "tda1002x.h"
//--HB-- Seems that my E30 has 353
//#include "mt352.h"
//#include "mt352_priv.h"
//--HB-- Seems that my E30 has 353.
#include "zl10353.h"
#include "zl10353_priv.h"

/* debug */
//static int dvb_usb_anysee_debug = -1;
static int dvb_usb_anysee_debug;
module_param_named(debug, dvb_usb_anysee_debug, int, 0644);
MODULE_PARM_DESC(debug, "set debugging level (1=rc (or-able))."
	DVB_USB_DEBUG_STATUS);

static int anysee_ctrl_msg(struct dvb_usb_device *d, u8 *sbuf, u8 slen,
	u8 *rbuf, u8 rlen)
{
	int act_len, ret;
	u8 buf[64];
	static u8 seq; /* packet sequence number */

	memcpy(&buf[0], sbuf, slen);
	buf[60] = seq++;

	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
		return -EAGAIN;

	deb_xfer(">>> ");
	debug_dump(buf,  sizeof(buf), deb_xfer);
	/* send request */
	ret = usb_bulk_msg(d->udev, usb_sndbulkpipe(d->udev, 0x01), buf, sizeof(buf), &act_len, ANYSEE_USB_TIMEOUT);
	if (ret)
		err("%s: sending failed: %d (%d/%d)", __FUNCTION__, ret, sizeof(buf), act_len);
	else
		ret = act_len != sizeof(buf) ? -1 : 0;

	if (ret)
		return ret;

	/* receive 1st answer (skip, answer to previous call) */
	ret = usb_bulk_msg(d->udev, usb_rcvbulkpipe(d->udev, 0x81), buf, sizeof(buf), &act_len, ANYSEE_USB_TIMEOUT);
	if (ret) {
		err("%s: 1st receiving failed: %d", __FUNCTION__, ret);
		return ret;
	}
	deb_xfer("<<< ");
	debug_dump(buf, act_len, deb_xfer);

	/* receive 2nd answer */
	ret = usb_bulk_msg(d->udev, usb_rcvbulkpipe(d->udev, 0x81), buf, sizeof(buf), &act_len, ANYSEE_USB_TIMEOUT);
	if (ret) {
		err("%s: 2nd receiving failed: %d", __FUNCTION__, ret);
		return ret;
	}
	deb_xfer("<<< ");
	debug_dump(buf, act_len, deb_xfer);

	mutex_unlock(&d->i2c_mutex);

	/* read request, copy returned data to return buf */
	if(rbuf && rlen)
		memcpy(rbuf, buf, rlen);

	return 0;
}

static int anysee_read_reg(struct dvb_usb_device *d, u16 reg, u8 *val)
{
	u8 buf[] = {CMD_REG_READ, (reg & 0xff00) >> 8, reg & 0x00ff, 0x01};
	int ret;
	ret = anysee_ctrl_msg(d, buf, sizeof(buf), val, 1);
	deb_info("%s: reg:%04x val:%02x\n", __FUNCTION__, reg, *val);
	return ret;
}

static int anysee_write_reg(struct dvb_usb_device *d, u16 reg, u8 val)
{
	u8 buf[] = {CMD_REG_WRITE, (reg & 0xff00) >> 8, reg & 0x00ff, 0x01, val};
	deb_info("%s: reg:%04x val:%02x\n", __FUNCTION__, reg, val);
	return anysee_ctrl_msg(d, buf, sizeof(buf), NULL, 0);
}

static int anysee_get_board_id(struct dvb_usb_device *d, u8 *id)
{
	u8 buf[] = {CMD_GET_BOARD_ID};
	return anysee_ctrl_msg(d, buf, sizeof(buf), id, 3);
}

static int anysee_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff)
{
	u8 buf[] = {CMD_STREAMING_CTRL, (u8)onoff, 0x00};
	deb_info("%s: onoff:%02x\n", __FUNCTION__, onoff);
	return anysee_ctrl_msg(adap->dev, buf, sizeof(buf), NULL, 0);
}

static int anysee_led_ctrl(struct dvb_usb_device *d, u8 mode, u8 interval)
{
	u8 buf[] = {CMD_LED_AND_IR_CTRL, 0x01, mode, interval};
	deb_info("%s: state:%02x interval:%02x\n", __FUNCTION__, mode, interval);
	return anysee_ctrl_msg(d, buf, sizeof(buf), NULL, 0);
}

static int anysee_ir_ctrl(struct dvb_usb_device *d, u8 onoff)
{
	u8 buf[] = {CMD_LED_AND_IR_CTRL, 0x02, onoff};
	deb_info("%s: onoff:%02x\n", __FUNCTION__, onoff);
	return anysee_ctrl_msg(d, buf, sizeof(buf), NULL, 0);
}

static int anysee_init(struct dvb_usb_device *d)
{
	int ret;
	/* LED light */
	ret = anysee_led_ctrl(d, 0x01, 0x03);
	if (ret)
		return ret;

	/* enable IR */
	ret = anysee_ir_ctrl(d, 1);
	if (ret)
		return ret;

	return 0;
}

/* I2C */
static int anysee_master_xfer(struct i2c_adapter* adap, struct i2c_msg *msg, int num)
{
	struct dvb_usb_device *d = i2c_get_adapdata(adap);
	int i = 0;
	int inc;
	int ret;

	while (i < num) {
		if (num > i + 1 && (msg[i+1].flags & I2C_M_RD)) {
			u8 buf[6];
			buf[0] = CMD_I2C_READ;
			buf[1] = msg[i].addr + 1;
			buf[2] = msg[i].buf[0];
			buf[3] = 0x00;
			buf[4] = 0x00;
			buf[5] = 0x01;
			ret = anysee_ctrl_msg(d, buf, sizeof(buf), msg[i+1].buf, msg[i+1].len);
			inc = 2;
		} else {
			u8 buf[4+msg[i].len];
			buf[0] = CMD_I2C_WRITE;
			buf[1] = msg[i].addr;
			buf[2] = msg[i].len;
			buf[3] = 0x01;
			memcpy(&buf[4], msg[i].buf, msg[i].len);
			ret = anysee_ctrl_msg(d, buf, sizeof(buf), NULL, 0);
			inc = 1;
		}
		if (ret)
			return ret;

		i += inc;
	}
	return i;
}

static u32 anysee_i2c_func(struct i2c_adapter *adapter)
{
	return I2C_FUNC_I2C;
}

static struct i2c_algorithm anysee_i2c_algo = {
	.master_xfer   = anysee_master_xfer,
	.functionality = anysee_i2c_func,
};

//--HB-- Seems that my E30 has 353
/*static int anysee_dee1601_mt352_demod_init(struct dvb_frontend* fe)
{
	static u8 clock_config []  = { CLOCK_CTL,  0x38, 0x28 };
	static u8 reset []         = { RESET,      0x80 };
	static u8 adc_ctl_1_cfg [] = { ADC_CTL_1,  0x40 };
	static u8 agc_cfg []       = { AGC_TARGET, 0x28, 0x20 };
	static u8 gpp_ctl_cfg []   = { GPP_CTL,    0x33 };
	static u8 capt_range_cfg[] = { CAPT_RANGE, 0x32 };

	mt352_write(fe, clock_config,   sizeof(clock_config));
	udelay(200);
	mt352_write(fe, reset,          sizeof(reset));
	mt352_write(fe, adc_ctl_1_cfg,  sizeof(adc_ctl_1_cfg));

	mt352_write(fe, agc_cfg,        sizeof(agc_cfg));
	mt352_write(fe, gpp_ctl_cfg,    sizeof(gpp_ctl_cfg));
	mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg));

	return 0;
}
*/

//--HB-- Looks like my E30 has 353
//--HB-- Lets try to remove this (Just following au6610)
//TODO: Needs to be checked. Now this is just copied from 352
/*static int anysee_dee1601_zl10353_demod_init(struct dvb_frontend* fe)
{
	static u8 clock_config []  = { CLOCK_CTL,  0x38, 0x28 };
	static u8 reset []         = { RESET,      0x80 };
	static u8 adc_ctl_1_cfg [] = { ADC_CTL_1,  0x40 };
	static u8 agc_cfg []       = { AGC_TARGET, 0x28, 0x20 };
	static u8 gpp_ctl_cfg []   = { GPP_CTL,    0x33 };
	static u8 capt_range_cfg[] = { CAPT_RANGE, 0x32 };

	zl10353_write(fe, clock_config,   sizeof(clock_config));
	udelay(200);
	zl10353_write(fe, reset,          sizeof(reset));
	zl10353_write(fe, adc_ctl_1_cfg,  sizeof(adc_ctl_1_cfg));

	zl10353_write(fe, agc_cfg,        sizeof(agc_cfg));
	zl10353_write(fe, gpp_ctl_cfg,    sizeof(gpp_ctl_cfg));
	zl10353_write(fe, capt_range_cfg, sizeof(capt_range_cfg));

	return 0;
}
*/

/* Callbacks for DVB USB */
static int anysee_identify_state(struct usb_device *udev,
				 struct dvb_usb_device_properties *props,
				 struct dvb_usb_device_description **desc,
				 int *cold)
{
	*cold = 0;
	return 0;
}

static int anysee_rc_query(struct dvb_usb_device *d, u32 *event, int *state)
{
	u8 buf[] = {CMD_GET_IR_CODE};
	struct dvb_usb_rc_key *keymap = d->props.rc_key_map;
	u8 ircode[2];
	int i, ret;

	ret = anysee_ctrl_msg(d, buf, sizeof(buf), &ircode[0], 2);
	if (ret)
		return ret;

	*event = 0;
	*state = REMOTE_NO_KEY_PRESSED;

	for (i = 0; i < d->props.rc_key_map_size; i++) {
		if (keymap[i].custom == ircode[0] &&
		    keymap[i].data == ircode[1]) {
			*event = keymap[i].event;
			*state = REMOTE_KEY_PRESSED;
			return 0;
		}
	}
	return 0;
}

static struct tda1002x_config anysee_tda1002x_config = {
	.demod_address = 0x1a,
	.invert = 0,

	.xtal   = 16000000,
	.pll_m  = 11,
	.pll_p  = 3,
	.pll_n  = 1,
	.deltaf = 0xfed6,
};

//--HB-- Seems that my E30 has 353
/* static struct mt352_config anysee_dee1601_mt352_config = {
	.demod_address = 0x1e,
	.demod_init    = anysee_dee1601_mt352_demod_init,
};
*/

//--HB-- Looks like my E30 has zl10353
//TODO: Just copied from mt352 version
static struct zl10353_config anysee_dee1601_zl10353_config = {
	.demod_address = 0x1e,
    .parallel_ts = 1,
//	.demod_init    = anysee_dee1601_zl10353_demod_init,
};

/* Philips TDA10023 DVB-C demod */
static int anysee_tda1002x_frontend_attach(struct dvb_usb_adapter *adap)
{
	int ret;

	/* connect demod on IO port D */
	ret = anysee_write_reg(adap->dev, 0xb0, 0x25);
	if (ret)
		return ret;

	if ((adap->fe = dvb_attach(tda10023_attach, &anysee_tda1002x_config,
	    &adap->dev->i2c_adap, 0x48)) != NULL) {
		return 0;
	}

	return -EIO;
}

/* Unknown PLL inside of Samsung DTOS403IH102A tuner module */
static int anysee_unknown_2_tuner_attach(struct dvb_usb_adapter *adap)
{
	deb_info("%s: \n", __FUNCTION__);
	dvb_attach(dvb_pll_attach, adap->fe, 0xc0, &adap->dev->i2c_adap, DVB_PLL_UNKNOWN_2);
	return 0;
}

//--HB-- Seems that my E30 has 353
/* Zarlink MT352 DVB-T demod inside of Samsung DNOS4D4ZH102A NIM */
/*static int anysee_dee1601_mt352_frontend_attach(struct dvb_usb_adapter *adap)
{
	if ((adap->fe = dvb_attach(mt352_attach, &anysee_dee1601_mt352_config,
	      &adap->dev->i2c_adap)) != NULL) {
		return 0;
	}

	return -EIO;
}
*/

//--HB-- Looks like my E30 has 353
//TODO: This is just copied from 352 version
/*TODO: Zarlink MT352 DVB-T demod inside of Samsung DNOS4D4ZH102A NIM */
static int anysee_dee1601_zl10353_frontend_attach(struct dvb_usb_adapter *adap)
{
	if ((adap->fe = dvb_attach(zl10353_attach, &anysee_dee1601_zl10353_config,
	      &adap->dev->i2c_adap)) != NULL) {
		return 0;
	}

	return -EIO;
}

/* Thomson dtt7579 (not sure) PLL inside of Samsung DNOS4D4ZH102A NIM */
static int anysee_dee1601_dtt7579_tuner_attach(struct dvb_usb_adapter *adap)
{
	dvb_attach(dvb_pll_attach, adap->fe, 0x61, NULL, DVB_PLL_THOMSON_DTT7579);
	return 0;
}

static struct dvb_usb_rc_key anysee_rc_keys[] = {
	{ 0x01, 0x00, KEY_0 },
	{ 0x01, 0x01, KEY_1 },
	{ 0x01, 0x02, KEY_2 },
	{ 0x01, 0x03, KEY_3 },
	{ 0x01, 0x04, KEY_4 },
	{ 0x01, 0x05, KEY_5 },
	{ 0x01, 0x06, KEY_6 },
	{ 0x01, 0x07, KEY_7 },
	{ 0x01, 0x08, KEY_8 },
	{ 0x01, 0x09, KEY_9 },
	{ 0x01, 0x0a, KEY_POWER },
	{ 0x01, 0x0b, KEY_DOCUMENTS },    /* '*' */
	{ 0x01, 0x19, KEY_FAVORITES },
	{ 0x01, 0x20, KEY_SLEEP },
	{ 0x01, 0x21, KEY_MODE },         /* 4:3 / 16:9 select */
	{ 0x01, 0x22, KEY_ZOOM },
	{ 0x01, 0x47, KEY_TEXT },
	{ 0x01, 0x16, KEY_TV },           /* TV / radio select */
	{ 0x01, 0x1e, KEY_LANGUAGE },     /* Second Audio Program */
	{ 0x01, 0x1a, KEY_SUBTITLE },
	{ 0x01, 0x1b, KEY_CAMERA },       /* screenshot */
	{ 0x01, 0x42, KEY_MUTE },
	{ 0x01, 0x0e, KEY_MENU },
	{ 0x01, 0x0f, KEY_EPG },
	{ 0x01, 0x17, KEY_INFO },
	{ 0x01, 0x10, KEY_EXIT },
	{ 0x01, 0x13, KEY_VOLUMEUP },
	{ 0x01, 0x12, KEY_VOLUMEDOWN },
	{ 0x01, 0x11, KEY_CHANNELUP },
	{ 0x01, 0x14, KEY_CHANNELDOWN },
	{ 0x01, 0x15, KEY_OK },
	{ 0x01, 0x1d, KEY_RED },
	{ 0x01, 0x1f, KEY_GREEN },
	{ 0x01, 0x1c, KEY_YELLOW },
	{ 0x01, 0x44, KEY_BLUE },
	{ 0x01, 0x0c, KEY_SHUFFLE },      /* snapshot */
	{ 0x01, 0x48, KEY_STOP },
	{ 0x01, 0x50, KEY_PLAY },
	{ 0x01, 0x51, KEY_PAUSE },
	{ 0x01, 0x49, KEY_RECORD },
	{ 0x01, 0x18, KEY_PREVIOUS },     /* |<< */
	{ 0x01, 0x0d, KEY_NEXT },         /* >>| */
	{ 0x01, 0x24, KEY_PROG1 },        /* 'F1' */
	{ 0x01, 0x25, KEY_PROG2 },        /* 'F2' */
};

/* DVB USB Driver stuff */
static struct dvb_usb_device_properties anysee_identify_properties;
static struct dvb_usb_device_properties anysee_e30c_properties;
static struct dvb_usb_device_properties anysee_e30_properties;

static int anysee_probe(struct usb_interface *intf,
			const struct usb_device_id *id)
{
	struct dvb_usb_device *d;
	struct usb_host_interface *alt;
	struct usb_device *udev;
	struct dvb_usb_device_properties *anysee_properties;
	int ret;
	u8 board_id[3];

	if (intf->num_altsetting < 1)
		return -ENODEV;

	if ((ret = dvb_usb_device_init(intf, &anysee_identify_properties, THIS_MODULE, &d)) == 0) {
		if (d && (ret = anysee_get_board_id(d, board_id) != 0))
			return ret;
		deb_info("%s: board id:%02x %02x %02x\n", __FUNCTION__, board_id[0], board_id[1], board_id[2]);
	}
//TODO check if dvb_usb_device_init calling twice leak resources

	switch (board_id[0]) {
	case 0x0a: //0a 01 00
		anysee_properties = &anysee_e30c_properties;
		break;
	case 0x02: //02 02 01
		anysee_properties = &anysee_e30_properties;
		break;
	default:
		err("Unkown Anysee version:%02x %02x %02x. Please report the linux-dvb@xxxxxxxxxxxx", board_id[0], board_id[1], board_id[2]);
		/* try load Anysee E30 DVB-T as fallback */
		anysee_properties = &anysee_e30_properties;
		break;
	}

	if ((ret = dvb_usb_device_init(intf, anysee_properties, THIS_MODULE, &d)) == 0) {
		alt = usb_altnum_to_altsetting(intf, 0);

		if (alt == NULL) {
			deb_info("no alt found!\n");
			return -ENODEV;
		}
		ret = usb_set_interface(d->udev, alt->desc.bInterfaceNumber, alt->desc.bAlternateSetting);

		udev = interface_to_usbdev(intf);
		if (d && (ret = anysee_init(d)) != 0)
			return ret;
	}

	return ret;
}

static struct usb_device_id anysee_table [] = {
//	{ USB_DEVICE(USB_VID_CYPRESS, 123) },
	{ USB_DEVICE(USB_VID_CYPRESS, USB_PID_ANYSEE) },
	{ }		/* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, anysee_table);

/* device for identify board */
static struct dvb_usb_device_properties anysee_identify_properties = {
	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
	.usb_ctrl = DEVICE_SPECIFIC,

	.size_of_priv     = 0,
	.identify_state   = anysee_identify_state,
	.num_adapters = 0,
	.i2c_algo = &anysee_i2c_algo,
	.num_device_descs = 1,
	.devices = {
		{
			"Anysee USB2.0",
			{ &anysee_table[0], NULL },
			{ NULL },
		},
	}
};

/* Anysee E30C DVB-C */
static struct dvb_usb_device_properties anysee_e30c_properties = {
	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
	.usb_ctrl = DEVICE_SPECIFIC,

	.rc_interval      = 250,  /* windows driver uses 500ms */
	.rc_key_map       = anysee_rc_keys,
	.rc_key_map_size  = ARRAY_SIZE(anysee_rc_keys),
	.rc_query         = anysee_rc_query,

	.size_of_priv     = 0,
	.identify_state   = anysee_identify_state,
	.num_adapters = 1,
	.adapter = {
		{
			.streaming_ctrl   = anysee_streaming_ctrl,
			.frontend_attach  = anysee_tda1002x_frontend_attach,
			.tuner_attach     = anysee_unknown_2_tuner_attach,
			/* parameter for the MPEG2-data transfer */
			.stream = {
				.type = USB_BULK,
				.count = 8,
				.endpoint = 0x82,
				.u = {
					.bulk = {
						.buffersize = 512,
					}
				}
			},
		}
	},
	.i2c_algo = &anysee_i2c_algo,
	.num_device_descs = 1,
	.devices = {
		{
			"Anysee E30C DVB-C USB2.0",
			{ &anysee_table[0], NULL },
			{ NULL },
		},
	}
};

/* Anysee E30 DVB-T */
static struct dvb_usb_device_properties anysee_e30_properties = {
	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
	.usb_ctrl = DEVICE_SPECIFIC,

	.rc_interval      = 250,  /* windows driver uses 500ms */
	.rc_key_map       = anysee_rc_keys,
	.rc_key_map_size  = ARRAY_SIZE(anysee_rc_keys),
	.rc_query         = anysee_rc_query,

	.size_of_priv     = 0,
	.identify_state   = anysee_identify_state,
	.num_adapters = 1,
	.adapter = {
		{
			.streaming_ctrl   = anysee_streaming_ctrl,
			//--HB-- Lets change this. It seems that my Anysee E30 DVB-T has other frontend
			.frontend_attach  = anysee_dee1601_zl10353_frontend_attach,
			//--HB--.frontend_attach  = anysee_dee1601_mt352_frontend_attach,
			.tuner_attach     = anysee_dee1601_dtt7579_tuner_attach,
			/* parameter for the MPEG2-data transfer */
			.stream = {
				.type = USB_BULK,
				.count = 8,
				.endpoint = 0x82,
				.u = {
					.bulk = {
						.buffersize = 512,
					}
				}
			},
		},
	},
	.i2c_algo = &anysee_i2c_algo,
	.num_device_descs = 1,
	.devices = {
		{
			"Anysee E30 DVB-T USB2.0",
			{ &anysee_table[0], NULL },
			{ NULL },
		},
	}
};

static struct usb_driver anysee_driver = {
#if LINUX_VERSION_CODE <=  KERNEL_VERSION(2,6,15)
	.owner      = THIS_MODULE,
#endif
	.name       = "dvb_usb_anysee",
	.probe      = anysee_probe,
	.disconnect = dvb_usb_device_exit,
	.id_table   = anysee_table,
};

/* module stuff */
static int __init anysee_module_init(void)
{
	int ret;
	if ((ret = usb_register(&anysee_driver))) {
		err("usb_register failed. Error number %d", ret);
		return ret;
	}

	return 0;
}

static void __exit anysee_module_exit(void)
{
	/* deregister this driver from the USB subsystem */
	usb_deregister(&anysee_driver);
}

module_init (anysee_module_init);
module_exit (anysee_module_exit);

MODULE_AUTHOR("Antti Palosaari <crope@xxxxxx>");
MODULE_DESCRIPTION("Driver Anysee E30 DVB-C & DVB-T USB2.0");
MODULE_VERSION("0.1");
MODULE_LICENSE("GPL");

_______________________________________________
linux-dvb mailing list
linux-dvb@xxxxxxxxxxx
http://www.linuxtv.org/cgi-bin/mailman/listinfo/linux-dvb

[Index of Archives]     [Linux Media]     [Video 4 Linux]     [Asterisk]     [Samba]     [Xorg]     [Xfree86]     [Linux USB]

  Powered by Linux